#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&,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 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 resources; private array fleetData; array 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(obj).Supply; data.fleetMaxSupply = cast(obj).MaxSupply; } else { data.fleetStr = obj.getFleetStrength(); data.fleetMaxStr = obj.getFleetMaxStrength(); } data.nextStrUpdate = gameTime + randomd(1.8,2.2); } if(!showFleetSupply) drawFleetIcon(cast(obj), pos, data.fleetStr*0.001, data.fleetMaxStr*0.001); else drawFleetIcon(cast(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 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 list; DataList@ objs = playerEmpire.getAsteroids(); Object@ obj; while(receive(objs, obj)) { Asteroid@ asteroid = cast(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(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 list; DataList@ objs = playerEmpire.getQueuedColonizations(); Object@ obj; while(receive(objs, obj)) { Planet@ pl = cast(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(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(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(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(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(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(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(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(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(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 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(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 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(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 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 list; array 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 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(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(); } // }}}