Files
2018-07-17 14:15:37 +02:00

1973 lines
43 KiB
ActionScript

#priority init -500
import elements.BaseGuiElement;
import elements.GuiIconGrid;
import elements.GuiMarkupText;
import elements.GuiResources;
import elements.GuiButton;
import elements.GuiContextMenu;
import elements.MarkupTooltip;
import elements.GuiOverlay;
import elements.GuiSkinElement;
import elements.GuiSprite;
import elements.GuiPanel;
import elements.GuiInfluenceCard;
import dialogs.MessageDialog;
import util.icon_view;
import util.formatting;
import resources;
import influence;
import traits;
import ftl;
import artifacts;
import statuses;
import abilities;
import targeting.ObjectTarget;
import targeting.PointTarget;
import planet_levels;
from orbitals import getOrbitalModuleID;
from traits import getTraitID;
from overlays.ContextMenu import openContextMenu;
from obj_selection import selectObject, uiObject, selectedObjects;
import tabs.Tab;
import void zoomTabTo(Object@ obj) from "tabs.GalaxyTab";
import void openOverlay(Object@ obj) from "tabs.GalaxyTab";
import void doQuickExport(Object@,bool) from "commands";
import void doQuickExport(const array<Object@>&,bool) from "commands";
from gui import animate_speed;
export Quickbar;
const int QUICKBAR_WIDTH = 256;
const int ANIM_SPEED = 512;
class QuickOption : GuiContextOption {
Quickbar@ bar;
uint index;
QuickOption(Quickbar@ bar, uint index) {
@this.bar = bar;
this.index = index;
}
void call(GuiContextMenu@ menu) override {
bar.modes[index].closed = !bar.modes[index].closed;
}
};
class Quickbar : BaseGuiElement, Savable {
array<QuickbarMode@> modes;
GuiButton@ quickButton;
GuiButton@ helpButton;
Empire@ prevEmpire;
Quickbar(IGuiElement@ parent) {
super(parent, recti());
@quickButton = GuiButton(this, recti(), "...");
quickButton.noClip = true;
@helpButton = GuiButton(this, recti());
helpButton.setIcon(Sprite(spritesheet::MenuIcons, 3));
helpButton.noClip = true;
refresh();
}
void refresh() {
@prevEmpire = playerEmpire;
for(uint i = 0, cnt = modes.length; i < cnt; ++i)
modes[i].remove();
modes.length = 0;
auto@ flingTrait = getTrait("Fling");
auto@ scTrait = getTrait("StarChildren");
auto@ anTrait = getTrait("Ancient");
auto@ exTrait = getTrait("Extragalactic");
add(CardMode(this), closed=true);
if(exTrait !is null && playerEmpire.hasTrait(exTrait.id))
add(Beacons(this));
if(anTrait !is null && playerEmpire.hasTrait(anTrait.id))
add(Replicators(this));
else
add(FreeResources(this));
add(AutoImportMode(this));
add(DisabledResources(this));
if((scTrait is null || !playerEmpire.hasTrait(scTrait.id)) && (anTrait is null || !playerEmpire.hasTrait(anTrait.id)))
add(OverPressurePlanets(this));
add(NoPopResources(this), closed=true);
add(DecayingPlanets(this));
add(ColonizingPlanets(this));
add(ColonizeSafePlanets(this), closed=true);
add(SiegePlanets(this));
add(LaborPlanets(this), closed=true);
add(DefenseTargets(this), closed=true);
add(FlingBeacons(this), closed=(flingTrait is null || !playerEmpire.hasTrait(flingTrait.id)));
add(CombatFleets(this));
add(LowSupplyFleets(this));
add(AllFleets(this), closed=true);
add(CivilianFleets(this), closed=true);
if(playerEmpire.isUnlocked(subsystem::MothershipHull))
add(Motherships(this));
add(DefenseStations(this), closed=true);
add(ArtifactMode(this));
updateAbsolutePosition();
}
void save(SaveFile& file) {
uint cnt = modes.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file << getClass(modes[i]).name;
file << modes[i].closed;
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
string name;
file >> name;
bool closed = false;
file >> closed;
for(uint n = 0, ncnt = modes.length; n < ncnt; ++n) {
if(getClass(modes[i]).name == name) {
modes[i].closed = closed;
break;
}
}
}
}
bool get_isRoot() const override {
return true;
}
void add(QuickbarMode@ mode, bool closed = false) {
mode.closed = closed;
modes.insertLast(mode);
}
void openMenu() {
GuiContextMenu menu(mousePos);
for(uint i = 0, cnt = modes.length; i < cnt; ++i) {
auto@ mode = modes[i];
auto@ option = QuickOption(this, i);
option.text = mode.name;
if(mode.closed)
option.icon = Sprite(spritesheet::ContextIcons, 10);
else
option.icon = Sprite(spritesheet::ContextIcons, 1);
menu.addOption(option);
}
menu.updateAbsolutePosition();
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.caller is quickButton && evt.type == GUI_Clicked) {
openMenu();
return true;
}
if(evt.caller is helpButton && evt.type == GUI_Clicked) {
showHelp();
return true;
}
return BaseGuiElement::onGuiEvent(evt);
}
void updateAbsolutePosition() override {
if(size.width != QUICKBAR_WIDTH)
size = vec2i(QUICKBAR_WIDTH, size.height);
int height = 4;
for(uint i = 0, cnt = modes.length; i < cnt; ++i) {
auto@ mode = modes[i];
bool visible = mode.show && !mode.closed;
if(!visible) {
if(mode.visible && !mode.removing) {
mode.removing = true;
mode.moveTo(vec2i(size.width, height));
}
continue;
}
else {
if(!mode.visible)
mode.position = vec2i(size.width, height);
mode.visible = true;
}
mode.updateAbsolutePosition();
if(mode.collapsed)
mode.moveTo(vec2i(max(size.width - 32, size.width - mode.size.width), height));
else
mode.moveTo(vec2i(max(0, size.width - mode.size.width), height));
height += mode.size.height + 4;
}
if(height != size.height)
size = vec2i(QUICKBAR_WIDTH, height);
position = vec2i(parent.size.width-size.width, 0);
if(quickButton !is null) {
quickButton.rect = recti_area(size.width-32,height, 32,32);
if(height+32 > parent.size.height) {
for(uint i = 0, cnt = modes.length; i < cnt; ++i) {
if(!modes[i].closed && modes[i].show) {
quickButton.rect = recti_area(modes[i].position - vec2i(36,0), vec2i(32,32));
break;
}
}
}
if(helpButton !is null)
helpButton.rect = quickButton.rect - vec2i(36, 0);
}
BaseGuiElement::updateAbsolutePosition();
}
void update(double time) {
if(prevEmpire !is playerEmpire)
refresh();
helpButton.visible = HELP_TEXT.length != 0;
bool changed = false;
for(uint i = 0, cnt = modes.length; i < cnt; ++i) {
auto@ mode = modes[i];
if(mode.closed) {
if(mode.visible && !mode.animating)
changed = true;
continue;
}
mode.update(time);
if(mode.show != mode.visible)
changed = true;
}
if(changed)
updateAbsolutePosition();
}
};
class QuickbarMode : BaseGuiElement {
GuiSprite@ picture;
Color color;
bool closed = false;
bool collapsed = false;
bool removing = false;
bool animating = false;
QuickbarMode(IGuiElement@ parent) {
super(parent, recti());
visible = false;
@picture = GuiSprite(this, Alignment(Left+2, Top+0.5f-18, Left+31, Top+0.5f+18), icon);
setMarkupTooltip(picture, name, hoverStyle=false);
}
string get_name() {
return "---";
}
Sprite get_icon() {
return Sprite();
}
int get_needHeight() {
return 32;
}
int get_maxHeight() {
return 400;
}
int get_needWidth() {
return 256;
}
bool get_show() {
return true;
}
Color get_tabColor() {
return Color();
}
void moveTo(const vec2i& pos) {
if(rect == recti()) {
rect = recti_area(pos, size);
}
else {
animate_speed(this, recti_area(pos, size), ANIM_SPEED);
animating = true;
}
}
bool onMouseEvent(const MouseEvent& evt, IGuiElement@ caller) override {
recti tabPos = recti_area(AbsolutePosition.topLeft, vec2i(32, size.height));
if(tabPos.isWithin(mousePos)) {
if(evt.type == MET_Button_Down && evt.button == 0) {
return true;
}
else if(evt.type == MET_Button_Up && evt.button == 0) {
collapsed = !collapsed;
parent.updateAbsolutePosition();
return true;
}
else if(evt.type == MET_Button_Down && evt.button == 1) {
return true;
}
else if(evt.type == MET_Button_Up && evt.button == 1) {
closed = true;
parent.updateAbsolutePosition();
return true;
}
}
return BaseGuiElement::onMouseEvent(evt, caller);
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Animation_Complete) {
animating = false;
if(removing) {
visible = false;
removing = false;
}
return true;
}
return BaseGuiElement::onGuiEvent(evt);
}
double timer = randomd(0.5, 1.0);
void update(double time) {
timer -= time;
if(timer <= 0) {
longUpdate();
timer += randomd(0.5, 1.0);
}
shortUpdate();
if(!animating) {
if(min(needHeight, maxHeight) != size.height || min(needWidth, 256) != size.width)
parent.updateAbsolutePosition();
}
}
void shortUpdate() {}
void longUpdate() {}
void updateAbsolutePosition() override {
if(parent !is null)
size = vec2i(min(needWidth, parent.size.width), min(needHeight, maxHeight));
BaseGuiElement::updateAbsolutePosition();
}
void draw() override {
recti boxPos = recti_area(AbsolutePosition.topLeft, size+vec2i(8, 0));
skin.draw(SS_Panel, SF_Normal, boxPos);
skin.draw(SS_SmallHexPattern, SF_Normal, boxPos.padded(3, 3));
recti tabPos = recti_area(AbsolutePosition.topLeft, vec2i(32, size.height));
skin.draw(SS_FullTitle, SF_Normal, tabPos, tabColor);
BaseGuiElement::draw();
}
};
final class FleetData {
double fleetStr = 0, fleetMaxStr = 0, nextStrUpdate = -1;
double fleetSupply = 0, fleetMaxSupply = 0;
int updateMax = 3;
};
//{{{ Object Modes
class ObjectGrid : GuiIconGrid {
private array<Resource@> resources;
private array<FleetData> fleetData;
array<int> counts;
bool showFleetSupply = false;
bool showManage = false;
string ttip;
vec2i pressedPos;
bool pressed = false;
ObjectGrid(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
iconSize = vec2i(46, 46);
spacing.y = 0;
}
void activateTooltip(string text) {
ttip = text;
MarkupTooltip tt(400, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
auto@ obj = resources[hovered].origin;
return format(ttip, obj.name);
}
uint get_length() override {
return resources.length;
}
void setHovered(int prev, int current) {
if(prev != hovered) {
if(hovered == -1 || uint(hovered) >= resources.length)
@uiObject = null;
else
@uiObject = resources[hovered].origin;
}
}
void drawElement(uint index, const recti& pos) override {
if(hovered == int(index) || resources[index].origin.selected)
drawRectangle(pos.padded(2, 3), color=Color(0xffffff30));
Object@ obj = resources[index].origin;
if(obj !is null) {
if(obj.isShip && obj.hasLeaderAI) {
FleetData@ data = fleetData[index];
if(gameTime >= data.nextStrUpdate) {
if(data.nextStrUpdate >= 0) {
if(data.updateMax <= 0) {
data.fleetMaxStr = obj.getFleetMaxStrength();
data.updateMax = 3;
}
else {
data.fleetStr = obj.getFleetStrength();
data.updateMax -= 1;
}
data.fleetSupply = cast<Ship>(obj).Supply;
data.fleetMaxSupply = cast<Ship>(obj).MaxSupply;
}
else {
data.fleetStr = obj.getFleetStrength();
data.fleetMaxStr = obj.getFleetMaxStrength();
}
data.nextStrUpdate = gameTime + randomd(1.8,2.2);
}
if(!showFleetSupply)
drawFleetIcon(cast<Ship>(obj), pos, data.fleetStr*0.001, data.fleetMaxStr*0.001);
else
drawFleetIcon(cast<Ship>(obj), pos, data.fleetSupply, data.fleetMaxSupply,
Color(0x4a9487ff), Color(0x4a9487ff));
}
else {
drawObjectIcon(obj, pos, resources[index]);
}
int count = counts[index];
if(count != 1) {
Colorf shifted;
Empire@ owner = obj.owner;
if(owner !is null)
shifted = Colorf(owner.color);
shifted.fromHSV((shifted.hue + 180.f)%360.f, shifted.saturation, shifted.value);
skin.getFont(FT_Bold).draw(horizAlign=0.85, vertAlign=0.85, text=count,
pos=pos, color=Color(shifted),
stroke=colors::Black);
}
}
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Clicked) {
if(hovered == -1)
return true;
auto@ r = resources[hovered];
if(evt.value == 0) {
if(r.origin.selected || ctrlKey) {
selectObject(r.origin);
zoomTabTo(r.origin);
if(showManage || ctrlKey)
openOverlay(r.origin);
}
else
selectObject(r.origin);
}
else if(evt.value == 1) {
openContextMenu(r.origin);
}
else if(evt.value == 2) {
zoomTabTo(r.origin);
}
return true;
}
return GuiIconGrid::onGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& evt, IGuiElement@ source) override {
if(source is this) {
if(evt.type != MET_Moved)
updateHover();
switch(evt.type) {
case MET_Moved:
if(hovered != -1 && uint(hovered) <= resources.length && pressed) {
Object@ obj = resources[hovered].origin;
if(evt.type == MET_Moved && pressedPos.distanceTo(mousePos) > 4 && mouseLeft) {
if(obj !is null && obj.selected)
doQuickExport(selectedObjects, true);
else if(obj !is null && obj.hasResources && getResource(obj.primaryResourceType) !is null)
doQuickExport(obj, true);
pressed = false;
}
}
updateHover();
break;
case MET_Button_Down:
if(uint(hovered) < length && evt.button == 0) {
pressed = true;
pressedPos = mousePos;
}
break;
case MET_Button_Up:
if(evt.button == 0)
pressed = false;
break;
}
}
return GuiIconGrid::onMouseEvent(evt, source);
}
void add(Object@ obj) {
set(resources.length, obj);
}
void set(uint index, Object@ obj) {
if(index >= resources.length) {
resources.insertLast(Resource());
counts.insertLast(1);
fleetData.length = fleetData.length + 1;
}
counts[index] = 1;
if(!obj.hasResources || !receive(obj.getNativeResources(), resources[index])) {
@resources[index].type = null;
@resources[index].origin = obj;
}
}
void set(uint index, ObjectData@ dat) {
if(index >= resources.length) {
resources.insertLast(dat.resource);
counts.insertLast(1);
fleetData.length = fleetData.length + 1;
}
@resources[index] = dat.resource;
@resources[index].origin = dat.obj;
fleetData[index] = FleetData();
counts[index] = 1;
}
void truncate(uint index, bool sort = true) {
if(resources.length > index) {
resources.length = index;
fleetData.length = index;
counts.length = index;
}
if(sort)
resources.sortDesc();
updateHover();
}
};
class ObjectMode : QuickbarMode {
ObjectGrid@ grid;
ObjectMode(IGuiElement@ parent) {
super(parent);
@grid = ObjectGrid(this, Alignment(Left+32, Top, Right, Bottom-4));
grid.horizAlign = 1.0;
}
bool get_show() override {
return grid.length != 0;
}
int get_needHeight() override {
int perRow = size.width / 40;
if(perRow == 0)
return 20;
return max(ceil(double(grid.length) / double(perRow)), 1.0) * 49;
}
int get_needWidth() override {
return grid.length * 50 + 40;
}
bool filter(ObjectData@ obj) {
return true;
}
void longUpdate() override {
uint index = 0;
for(uint i = 0, cnt = empirePlanets.length; i < cnt; ++i) {
if(!filter(empirePlanets[i]))
continue;
grid.set(index, empirePlanets[i]);
++index;
}
grid.truncate(index);
}
};
bool sentFreePlanetsWarning = false;
int freeWarningDelay = 180;
class FreeResources : ObjectMode {
FreeResources(IGuiElement@ parent) {
super(parent);
color = Color(0x00fa99ff);
}
string get_name() override {
return locale::FREE_RESOURCES;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 2);
}
void longUpdate() override {
uint unusedPlanets = 0;
array<uint> types(getResourceCount(), UINT_MAX);
uint index = 0;
for(uint i = 0, cnt = empirePlanets.length; i < cnt; ++i) {
auto@ dat = empirePlanets[i];
if(!filter(dat))
continue;
if(dat.obj.level == 0)
++unusedPlanets;
if(dat.resource.type !is null) {
uint tID = dat.resource.type.id;
if(types[tID] != UINT_MAX) {
grid.counts[types[tID]]++;
continue;
}
types[tID] = index;
}
grid.set(index, dat);
++index;
}
if(!sentFreePlanetsWarning) {
if(unusedPlanets >= 5 && playerEmpire.TotalBudget < playerEmpire.MaintenanceBudget) {
if(--freeWarningDelay == 0) {
auto@ msg = message(locale::HINT_USE_PLANETS);
msg.addTitle(locale::HINT_USE_PLANETS_TITLE);
sentFreePlanetsWarning = true;
}
}
else {
freeWarningDelay = 180;
}
}
array<Asteroid@> list;
DataList@ objs = playerEmpire.getAsteroids();
Object@ obj;
while(receive(objs, obj)) {
Asteroid@ asteroid = cast<Asteroid>(obj);
if(asteroid !is null && asteroid.nativeResourceCount != 0)
list.insertLast(asteroid);
}
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
auto@ dat = cache(list[i]);
if(!filter(dat))
continue;
grid.set(index, dat);
++index;
}
grid.truncate(index, sort=false);
}
bool filter(ObjectData@ dat) {
Object@ obj = dat.obj;
auto@ type = dat.resource.type;
if(type is null)
return false;
Object@ dest = dat.destination;
if(dest !is null) {
Empire@ destOwner = dest.owner;
if(destOwner is playerEmpire || destOwner is null || !destOwner.valid)
return false;
}
if(!dat.resource.usable)
return false;
if(obj.isPlanet) {
if(type.isMaterial(obj.level))
return false;
if(obj.decayTime > 0)
return false;
if(obj.resourceLevel > obj.level)
return false;
if(!obj.exportEnabled)
return false;
}
if(!type.exportable || type.mode == RM_NonRequirement)
return false;
return true;
}
};
class DisabledResources : ObjectMode {
DisabledResources(IGuiElement@ parent) {
super(parent);
color = Color(0xfa0099ff);
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 3);
}
bool filter(ObjectData@ dat) {
auto@ type = dat.resource.type;
if(type is null)
return false;
if(!dat.resource.usable) {
double pop = dat.obj.population;
if(!dat.obj.inCombat && pop < getPlanetLevelRequiredPop(dat.obj, dat.obj.resourceLevel))
return false;
if(dat.obj.population < 1.0)
return false;
return true;
}
return false;
}
string get_name() override {
return locale::DISABLED_RESOURCES;
}
};
class OverPressurePlanets : ObjectMode {
OverPressurePlanets(IGuiElement@ parent) {
super(parent);
color = Color(0xfa0099ff);
}
Sprite get_icon() override {
return icons::Pressure;
}
bool filter(ObjectData@ dat) {
if(!dat.obj.isPlanet)
return false;
if(dat.obj.level == 0)
return false;
return dat.obj.isOverPressure;
}
string get_name() override {
return locale::OVERPRESSURE_PLANETS;
}
};
class Replicators : ObjectMode {
uint statusId = uint(-1);
uint orbId = uint(-1);
Replicators(IGuiElement@ parent) {
super(parent);
auto@ status = getStatusType("AncientReplicator");
if(status !is null)
statusId = status.id;
orbId = getOrbitalModuleID("AncientReplicator");
}
Sprite get_icon() override {
return spritesheet::GuiOrbitalIcons+20;
}
bool filter(ObjectData@ dat) {
if(!dat.obj.isPlanet)
return false;
return dat.obj.hasStatusEffect(statusId);
}
string get_name() override {
return locale::REPLICATORS;
}
void longUpdate() override {
uint index = 0;
for(uint i = 0, cnt = empirePlanets.length; i < cnt; ++i) {
if(!filter(empirePlanets[i]))
continue;
grid.set(index, empirePlanets[i]);
++index;
}
DataList@ objs = playerEmpire.getOrbitals();
Object@ obj;
while(receive(objs, obj)) {
Orbital@ orb = cast<Orbital>(obj);
if(orb !is null && uint(orb.coreModule) == orbId) {
if(!orb.inOrbit && !orb.hasLockedOrbit()) {
auto@ dat = cache(orb);
grid.set(index, dat);
++index;
}
}
}
grid.truncate(index);
}
};
class NoPopResources : ObjectMode {
NoPopResources(IGuiElement@ parent) {
super(parent);
color = Color(0xfa0099ff);
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 3, colors::Green);
}
bool filter(ObjectData@ dat) {
auto@ type = dat.resource.type;
if(type is null)
return false;
if(!dat.resource.usable) {
if(dat.obj.population >= getPlanetLevelRequiredPop(dat.obj, dat.obj.resourceLevel))
return false;
if(dat.obj.inCombat)
return false;
return true;
}
return false;
}
string get_name() override {
return locale::NO_POP_RESOURCES;
}
};
class DecayingPlanets : ObjectMode {
DecayingPlanets(IGuiElement@ parent) {
super(parent);
color = Color(0xfaf099ff);
}
bool filter(ObjectData@ dat) {
if(dat.obj.decayTime > 0)
return true;
return false;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 4);
}
string get_name() override {
return locale::DECAYING_PLANETS;
}
};
bool shownColonizeWarning = false;
uint colonizeWarningDelay = 100;
class ColonizingPlanets : ObjectMode {
ColonizingPlanets(IGuiElement@ parent) {
super(parent);
color = Color(0x8060ffff);
}
bool filter(ObjectData@ dat) {
if(dat.obj.population < 1.0 || dat.obj.isBeingColonized)
return true;
return false;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 5);
}
string get_name() override {
return locale::COLONIZING_PLANETS;
}
void longUpdate() override {
uint index = 0;
for(uint i = 0, cnt = empirePlanets.length; i < cnt; ++i) {
if(!filter(empirePlanets[i]))
continue;
grid.set(index, empirePlanets[i]);
++index;
}
if(!shownColonizeWarning) {
int colonyCost = -playerEmpire.getMoneyFromType(0);
int budget = playerEmpire.TotalBudget;
int netBudget = budget - playerEmpire.MaintenanceBudget;
if(netBudget < 0 && colonyCost > 200 && colonyCost > budget/6) {
if(--colonizeWarningDelay == 0) {
auto@ msg = message(locale::HINT_COLONIZE);
msg.addTitle(locale::HINT_COLONIZE_TITLE);
shownColonizeWarning = true;
}
}
else {
colonizeWarningDelay = 100;
}
}
set_int listed;
array<Planet@> list;
DataList@ objs = playerEmpire.getQueuedColonizations();
Object@ obj;
while(receive(objs, obj)) {
Planet@ pl = cast<Planet>(obj);
if(pl !is null && !listed.contains(pl.id)) {
listed.insert(pl.id);
list.insertLast(pl);
}
}
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
auto@ dat = cache(list[i]);
if(!filter(dat))
continue;
grid.set(index, dat);
++index;
}
grid.truncate(index, sort=false);
}
};
class ColonizeSafePlanets : ObjectMode {
ColonizeSafePlanets(IGuiElement@ parent) {
super(parent);
}
bool filter(ObjectData@ dat) {
return dat.obj.canSafelyColonize;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 5, Color(0x00ff00ff));
}
string get_name() override {
return locale::COLONIZE_SAFE_PLANETS;
}
};
class LaborPlanets : ObjectMode {
LaborPlanets(IGuiElement@ parent) {
super(parent);
color = Color(0x606080ff);
grid.showManage = true;
}
bool filter(ObjectData@ dat) {
if(dat.obj.getResourceProduction(TR_Labor) > 0)
return true;
return false;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 1);
}
string get_name() override {
return locale::LABOR_PLANETS;
}
void longUpdate() override {
uint index = 0;
for(uint i = 0, cnt = empirePlanets.length; i < cnt; ++i) {
if(!filter(empirePlanets[i]))
continue;
grid.set(index, empirePlanets[i]);
++index;
}
DataList@ objs = playerEmpire.getOrbitals();
Object@ obj;
while(receive(objs, obj)) {
Orbital@ orb = cast<Orbital>(obj);
if(orb !is null && orb.hasConstruction) {
auto@ dat = cache(orb);
grid.set(index, dat);
++index;
}
}
grid.truncate(index);
}
};
class DefenseTargets : ObjectMode {
DefenseTargets(IGuiElement@ parent) {
super(parent);
color = Color(0x606080ff);
grid.activateTooltip(format("[font=Medium]$1[/font]\n$2",
"$1", locale::TT_IS_DEFENDING));
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 8);
}
string get_name() override {
return locale::DEFENSE_TARGETS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getDefending();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
if(obj !is null) {
auto@ dat = cache(obj);
grid.set(index, dat);
++index;
}
}
grid.truncate(index);
}
};
class SiegePlanets : ObjectMode {
SiegePlanets(IGuiElement@ parent) {
super(parent);
color = Color(0xfaf099ff);
}
bool filter(ObjectData@ dat) {
Empire@ captEmp = dat.obj.captureEmpire;
if(captEmp is null || captEmp is playerEmpire)
return false;
return true;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 7);
}
Color get_tabColor() override {
return Color(0xff0000ff);
}
string get_name() override {
return locale::UNDER_SIEGE_PLANETS;
}
};
class FlingBeacons : ObjectMode {
FlingBeacons(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::GuiOrbitalIcons, 0);
}
string get_name() override {
return locale::FLING_BEACONS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlingBeacons();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Orbital@ beacon = cast<Orbital>(obj);
if(beacon !is null) {
auto@ dat = cache(beacon);
grid.set(index, dat);
++index;
}
}
grid.truncate(index);
}
};
class Beacons : ObjectMode {
Beacons(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::GuiOrbitalIcons, 0);
}
string get_name() override {
return locale::ORB_BEACON;
}
void longUpdate() override {
auto@ core = getOrbitalModule("Beacon");
uint index = 0;
DataList@ objs = playerEmpire.getOrbitals();
Object@ obj;
while(receive(objs, obj)) {
Orbital@ orb = cast<Orbital>(obj);
if(orb !is null && core !is null && orb.coreModule == core.id) {
auto@ dat = cache(orb);
grid.set(index, dat);
++index;
}
}
grid.truncate(index);
}
};
class AllFleets : ObjectMode {
AllFleets(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::ShipIcons, 0);
}
string get_name() override {
return locale::ALL_FLEETS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlagships();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null && !ship.blueprint.design.hasTag(ST_Mothership)) {
if(ship.getFleetMaxStrength() < 1000.0)
continue;
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class CivilianFleets : ObjectMode {
CivilianFleets(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::ShipIcons, 0);
}
string get_name() override {
return locale::CIVILIAN_FLEETS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlagships();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null && !ship.blueprint.design.hasTag(ST_Mothership)) {
if(ship.getFleetMaxStrength() > 1000.0)
continue;
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class Motherships : ObjectMode {
Motherships(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::ShipIcons, 0);
}
string get_name() override {
return locale::MOTHERSHIPS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlagships();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.blueprint.design.hasTag(ST_Mothership)) {
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class DefenseStations : ObjectMode {
DefenseStations(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::GuiOrbitalIcons, 0);
}
string get_name() override {
return locale::DEFENSE_STATIONS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getStations();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null) {
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class LowSupplyFleets : ObjectMode {
LowSupplyFleets(IGuiElement@ parent) {
super(parent);
grid.showFleetSupply = true;
}
Sprite get_icon() override {
return icons::Supply;
}
string get_name() override {
return locale::FLEETS_LOW_SUPPLY;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlagships();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.Supply < ship.MaxSupply * 0.5) {
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class CombatFleets : ObjectMode {
CombatFleets(IGuiElement@ parent) {
super(parent);
}
Sprite get_icon() override {
return Sprite(spritesheet::ShipIcons, 0);
}
Color get_tabColor() override {
return Color(0xff0000ff);
}
string get_name() override {
return locale::COMBAT_FLEETS;
}
void longUpdate() override {
DataList@ objs = playerEmpire.getFlagships();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.inCombat) {
grid.set(index, ship);
++index;
}
}
grid.truncate(index);
}
};
class ObjectData {
Object@ obj;
int resMod;
Resource resource;
Object@ destination;
};
double timer = randomd(0.5, 2.0);
dictionary objCache;
array<ObjectData@> empirePlanets;
void update(ObjectData@ dat) {
if(dat.obj.hasResources) {
receive(dat.obj.getNativeResources(), dat.resource);
@dat.destination = dat.obj.nativeResourceDestination[0];
}
}
ObjectData@ cache(Object@ obj) {
ObjectData@ dat;
if(objCache.get(obj.id, @dat)) {
int newMod = obj.hasResources ? obj.resourceModID : 0;
if(newMod != dat.resMod) {
dat.resMod = newMod;
update(dat);
}
return dat;
}
@dat = ObjectData();
@dat.obj = obj;
dat.resMod = obj.hasResources ? obj.resourceModID : 0;
objCache.set(obj.id, @dat);
update(dat);
return dat;
}
void tick(double time) {
timer -= time;
if(timer <= 0) {
empirePlanets.length = 0;
DataList@ objs = playerEmpire.getPlanets();
Object@ obj;
while(receive(objs, obj)) {
Planet@ pl = cast<Planet>(obj);
if(pl !is null)
empirePlanets.insertLast(cache(pl));
}
timer += randomd(0.5, 2.0);
}
}
//}}}
//{{{ Influence Mode
const int CARD_W = 55;
const int CARD_H = 36;
class CardGrid : GuiIconGrid {
array<StackInfluenceCard@> cards;
CardGrid(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
iconSize = vec2i(CARD_W, CARD_H);
spacing.y = 0;
MarkupTooltip tt(400, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
uint get_length() override {
return cards.length;
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
return cards[hovered].formatTooltip(playerEmpire);
}
void drawElement(uint index, const recti& pos) override {
if(hovered == int(index))
drawRectangle(pos.padded(2, 2), color=Color(0xffffff30));
auto@ card = cards[index];
Color col;
if(card.purchasedBy !is null)
col = card.purchasedBy.color;
skin.draw(SS_LightPanel, SF_Normal, pos.padded(3, 3), col);
drawCardIcon(card, pos);
}
IGuiElement@ findTab() {
IGuiElement@ elem = parent;
while(elem !is null && cast<Tab>(elem) is null)
@elem = elem.parent;
return elem;
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Clicked) {
if(hovered == -1)
return true;
if(ctrlKey) {
sound::card_draw.play(priority=true);
buyCardFromInfluenceStack(cards[hovered].id);
emitConfirmed();
}
else {
sound::card_examine.play(priority=true);
StackInfluenceCard card = cards[hovered];
GuiInfluenceCardPopup(findTab(), parent, card);
}
return true;
}
return GuiIconGrid::onGuiEvent(evt);
}
void add(StackInfluenceCard@ card) {
set(cards.length, card);
}
void set(uint index, StackInfluenceCard@ card) {
if(index >= cards.length)
cards.insertLast(card);
else
@cards[index] = card;
}
void truncate(uint index) {
if(cards.length > index)
cards.length = index;
updateHover();
}
};
class CardMode : QuickbarMode {
CardGrid@ grid;
array<StackInfluenceCard> cards;
double stackTimer = 0.0;
CardMode(IGuiElement@ parent) {
super(parent);
@grid = CardGrid(this, Alignment(Left+36, Top+1, Right-4, Bottom-4));
color = Color(0x20adffff);
}
bool get_show() override {
return grid.length != 0;
}
Sprite get_icon() override {
return Sprite(spritesheet::QuickbarIcons, 0);
}
string get_name() override {
return locale::INFLUENCE_STACK;
}
int get_needHeight() {
int perRow = size.width / (CARD_W + 4);
if(perRow == 0)
return CARD_H + 4;
return max(ceil(double(grid.length) / double(perRow)), 1.0) * (CARD_H + 4);
}
int get_needWidth() {
return min(grid.length, 3) * (CARD_W + 4) + 40;
}
void shortUpdate() {
QuickbarMode::shortUpdate();
double prevTimer = stackTimer;
stackTimer = getInfluenceDrawTimer();
if(prevTimer < stackTimer)
longUpdate();
}
void longUpdate() {
cards.syncFrom(getInfluenceCardStack());
uint i = 0;
for(uint cnt = cards.length; i < cnt; ++i)
grid.set(i, cards[i]);
grid.truncate(i);
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Confirmed) {
longUpdate();
timer = 0.15;
return true;
}
return QuickbarMode::onGuiEvent(evt);
}
void draw() override {
QuickbarMode::draw();
string timeText = formatEstTime(stackTimer);
const Font@ ft = skin.getFont(FT_Small);
ft.draw(pos=picture.absolutePosition.padded(0, 3), text=timeText, horizAlign=0.5, vertAlign=1.0);
}
};
//}}}
//{{{ Auto Imports Mode
class AutoImportGrid : GuiIconGrid {
array<AutoImportDesc> list;
array<uint> counts;
AutoImportGrid(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
iconSize = vec2i(36, 36);
spacing.y = 0;
MarkupTooltip tt(400, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
auto@ desc = list[hovered];
string type;
if(desc.type !is null)
type = desc.type.name;
else if(desc.cls !is null)
type = desc.cls.name;
else if(desc.level != -1)
type = locale::LEVEL+" "+desc.level;
return format(locale::TT_AUTO_IMPORTS, toString(counts[hovered]), type,
getSpriteDesc(getAutoImportIcon(desc)));
}
uint get_length() override {
return list.length;
}
void drawElement(uint index, const recti& pos) override {
auto@ desc = list[index];
getAutoImportIcon(desc).draw(pos.padded(4));
uint count = counts[index];
if(count > 1) {
skin.getFont(FT_Bold).draw(horizAlign=0.85, vertAlign=0.85, text=count,
pos=pos, color=Color(0xf268ffff),
stroke=colors::Black);
}
}
void add(AutoImportDesc@ desc, uint& index) {
for(uint i = 0; i < index; ++i) {
if(list[i].equivalent(desc)) {
counts[i] += 1;
return;
}
}
set(index, desc);
index += 1;
}
void set(uint index, AutoImportDesc@ desc) {
if(index >= list.length) {
list.insertLast(desc);
counts.insertLast(1);
}
else {
list[index] = desc;
counts[index] = 1;
}
}
void truncate(uint index) {
if(list.length > index) {
list.length = index;
counts.length = index;
}
updateHover();
}
};
class AutoImportMode : QuickbarMode {
AutoImportGrid@ grid;
AutoImportMode(IGuiElement@ parent) {
super(parent);
@grid = AutoImportGrid(this, Alignment(Left+36, Top+1, Right-4, Bottom-4));
}
bool get_show() override {
return grid.length != 0;
}
Sprite get_icon() override {
return Sprite(spritesheet::ContextIcons, 0);
}
string get_name() override {
return locale::AUTO_IMPORTS;
}
int get_needHeight() override {
int perRow = size.width / 30;
if(perRow == 0)
return 20;
return max(ceil(double(grid.length) / double(perRow)), 1.0) * 39;
}
int get_needWidth() override {
return grid.length * 40 + 30;
}
AutoImportDesc desc;
void longUpdate() {
DataList@ list = playerEmpire.getAutoImports();
uint index = 0;
while(receive(list, desc))
grid.add(desc, index);
grid.truncate(index);
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Confirmed) {
longUpdate();
timer = 0.15;
return true;
}
return QuickbarMode::onGuiEvent(evt);
}
};
//}}}
//{{{ Artifact Mode
const int ARTIF_W = 45;
const int ARTIF_H = 36;
dictionary ablCache;
class ArtifDesc {
Artifact@ artif;
uint count = 1;
const ArtifactType@ type;
Ability@ ability;
ArtifDesc(Artifact@ obj) {
set(obj);
}
ArtifDesc() {
}
void set(Artifact@ obj) {
@artif = obj;
@type = getArtifactType(obj.ArtifactType);
if(!ablCache.get(obj.id, @ability)) {
@ability = Ability();
receive(obj.getAbilities(), ability);
@ability.emp = playerEmpire;
ablCache.set(obj.id, @ability);
}
count = 1;
}
int opCmp(const ArtifDesc@ other) const {
if(ability.type is null)
return -1;
if(other.ability.type is null)
return 1;
double myCost = ability.getEnergyCost();
double theirCost = other.ability.getEnergyCost();
if(myCost > theirCost)
return 1;
if(theirCost > myCost)
return -1;
return 0;
}
};
class ArtifGrid : GuiIconGrid {
array<ArtifDesc@> list;
ArtifGrid(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
iconSize = vec2i(ARTIF_W, ARTIF_H);
spacing.y = 0;
MarkupTooltip tt(400, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
uint get_length() override {
return list.length;
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
auto@ desc = list[hovered];
if(desc.ability.type is null)
return "";
string tt = format("[font=Medium][b]$1[/b][/font] [img=$2;24/]\n",
desc.type.name, getSpriteDesc(desc.type.icon));
tt += desc.ability.formatTooltip(playerEmpire);
return tt;
}
void drawElement(uint index, const recti& pos) override {
auto@ desc = list[index];
Color col(0xffffffff);
if(desc.ability !is null && desc.ability.type !is null) {
auto@ abl = desc.ability;
if(abl.cooldown > 0 || abl.getEnergyCost() > playerEmpire.EnergyStored)
col = Color(0xff0000ff);
}
if(hovered == int(index))
drawRectangle(pos, Color(0xffffff30));
skin.draw(SS_LightPanel, SF_Normal, pos.padded(3, 3), col);
desc.type.icon.draw(recti_area(pos.topLeft + vec2i(5, 5), vec2i(ARTIF_H - 14, ARTIF_H - 14)));
if(desc.ability !is null && desc.ability.type !is null) {
auto@ abl = desc.ability;
abl.type.icon.draw(recti_area(pos.topLeft + vec2i(ARTIF_W - ARTIF_H + 9, 9), vec2i(ARTIF_H - 14, ARTIF_H - 14)));
}
//Draw amount
if(desc.count > 1) {
int x = pos.size.x - 9;
if(desc.count > 5) {
font::DroidSans_11_Bold.draw(
pos=recti_area(vec2i(pos.topLeft.x + x - 30, pos.topLeft.y + 4), vec2i(35, 10)),
text=toString(desc.count),
color=Color(0xe32020ff),
horizAlign=1.0);
}
else {
for(uint i = 0; i < desc.count; ++i) {
drawRectangle(recti_area(vec2i(pos.topLeft.x+x, pos.topLeft.y+4), vec2i(5, 5)), Color(0x991c1cff));
x -= 8;
}
}
}
}
void activate(ArtifDesc@ desc) {
if(desc.ability is null && desc.ability.type !is null)
return;
Ability abl = desc.ability;
if(abl.cooldown > 0 || abl.getEnergyCost() > playerEmpire.EnergyStored) {
zoomTabTo(desc.artif);
selectObject(desc.artif);
return;
}
@abl.emp = playerEmpire;
if(abl.type.targets.length == 0 || desc.artif.abilityCount > 1) {
zoomTabTo(desc.artif);
selectObject(desc.artif);
}
else if(abl.type.targets[0].type == TT_Point) {
targetPoint(AbilityTargetPoint(abl));
}
else if(abl.type.targets[0].type == TT_Object) {
targetObject(AbilityTargetObject(abl));
}
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Clicked) {
if(hovered == -1)
return true;
auto@ desc = list[hovered];
if(evt.value == 0)
activate(desc);
else if(evt.value == 2)
zoomTabTo(desc.artif);
return true;
}
return GuiIconGrid::onGuiEvent(evt);
}
void add(Artifact@ obj, uint& index) {
auto@ type = getArtifactType(obj.ArtifactType);
if(type.collapses) {
for(uint i = 0, cnt = min(list.length, index); i < cnt; ++i) {
if(list[i].type is type) {
list[i].count += 1;
return;
}
}
}
set(index, obj);
index += 1;
}
void set(uint index, Artifact@ obj) {
if(index >= list.length)
list.insertLast(ArtifDesc(obj));
else
list[index].set(obj);
}
void truncate(uint index) {
if(list.length > index)
list.length = index;
list.sortDesc();
updateHover();
}
};
class ArtifactMode : QuickbarMode {
ArtifGrid@ grid;
ArtifactMode(IGuiElement@ parent) {
super(parent);
@grid = ArtifGrid(this, Alignment(Left+36, Top+1, Right-4, Bottom-4));
@picture.alignment = Alignment(Left+1, Top+0.5f-15, Left+31, Top+0.5f+15);
}
bool get_show() override {
return grid.length != 0;
}
Sprite get_icon() override {
return Sprite(spritesheet::ArtifactIcon, 0);
}
string get_name() override {
return locale::AVAIL_ARTIFACTS;
}
int get_needHeight() {
int perRow = size.width / (ARTIF_W + 4);
if(perRow == 0)
return ARTIF_H + 4;
return max(ceil(double(grid.length) / double(perRow)), 1.0) * (ARTIF_H + 4);
}
int get_needWidth() {
return min(grid.length, 4) * (ARTIF_W + 4) + 40;
}
void longUpdate() {
DataList@ objs = playerEmpire.getArtifacts();
Object@ obj;
uint index = 0;
while(receive(objs, obj)) {
Artifact@ artif = cast<Artifact>(obj);
if(artif !is null)
grid.add(artif, index);
}
grid.truncate(index);
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.type == GUI_Confirmed) {
longUpdate();
timer = 0.15;
return true;
}
return QuickbarMode::onGuiEvent(evt);
}
};
//}}}
// {{{ Game help popup
class HelpOverlay : GuiOverlay {
GuiSkinElement@ bg;
GuiPanel@ panel;
GuiMarkupText@ page;
GuiButton@ okButton;
double prevGameSpeed = -1.0;
HelpOverlay() {
super(null);
closeSelf = false;
@bg = GuiSkinElement(this, Alignment(Left+0.2f, Top+0.1f, Right-0.2f, Bottom-0.1f), SS_PlainOverlay);
@panel = GuiPanel(bg, Alignment(Left, Top+2, Right-2, Bottom-60));
updateAbsolutePosition();
@page = GuiMarkupText(panel, recti_area(8, 2, panel.size.width-25, 100));
page.text = HELP_TEXT;
@okButton = GuiButton(bg, Alignment(Left+0.5f-100, Bottom-56, Left+0.5f+100, Bottom-4), locale::GOT_IT);
updateAbsolutePosition();
}
void close() {
if(prevGameSpeed >= 0) {
gameSpeed = prevGameSpeed;
prevGameSpeed = -1.0;
}
GuiOverlay::close();
}
void remove() {
if(prevGameSpeed >= 0) {
gameSpeed = prevGameSpeed;
prevGameSpeed = -1.0;
}
GuiOverlay::remove();
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.caller is okButton && evt.type == GUI_Clicked) {
close();
return true;
}
return GuiOverlay::onGuiEvent(evt);
}
};
string HELP_TEXT;
HelpOverlay@ overlay;
void addHelpText(const string& str) {
if(HELP_TEXT.length != 0)
HELP_TEXT += "\n\n\n[hr/]\n";
HELP_TEXT += str;
if(!isLoadedSave) {
if(overlay is null || overlay.parent is null) {
showHelp();
}
else {
overlay.page.text = HELP_TEXT;
overlay.updateAbsolutePosition();
}
}
}
void showHelp() {
@overlay = HelpOverlay();
if(!isServer && gameTime < 5.0 && gameSpeed != 0 && !isClient) {
overlay.prevGameSpeed = gameSpeed;
gameSpeed = 0;
}
}
void init() {
checkHelp();
}
void checkHelp() {
if(hasInvasionMap())
addHelpText(locale::HELP_INVASION);
if(playerEmpire.hasTrait(getTraitID("Ancient")))
addHelpText(locale::HELP_ANCIENT);
else if(playerEmpire.hasTrait(getTraitID("Extragalactic")))
addHelpText(locale::HELP_EXTRAGALACTIC);
}
void postReload(Message& msg) {
checkHelp();
}
// }}}