#section disable menu import elements.BaseGuiElement; import elements.GuiText; import elements.MarkupTooltip; import elements.GuiIconGrid; import resources; import util.formatting; import planet_levels; export GuiResources, GuiResourceRequirements; export GuiResourceGrid, GuiResourceReqGrid; export drawResource, drawSmallResource; export getAutoImportIcon; export getRequirementIcon; const ResourceClass@ foodClass; const ResourceClass@ waterClass; const ResourceType@ waterType; const ResourceClass@ scalableClass; void init() { @foodClass = getResourceClass("Food"); @scalableClass = getResourceClass("Scalable"); @waterClass = getResourceClass("WaterType"); @waterType = getResource("Water"); } void drawResource(const ResourceType@ type, const recti& pos) { type.icon.draw(pos); //Affinities uint affCnt = type.affinities.length; for(uint i = 0; i < affCnt; ++i) { recti affPos = recti_area(vec2i(pos.topLeft.x, pos.botRight.y-i*20-20), vec2i(20, 20)); getAffinitySprite(type.affinities[i]).draw(affPos); } //Resource level icon if(type.level > 0 && type.level <= 3) spritesheet::ResourceIconsMods.draw(2+type.level, pos); //Resource class icon else if(type.cls is foodClass) spritesheet::ResourceIconsMods.draw(6, pos); } void drawResource(const Resource@ r, const recti& pos, Object@ drawFrom = null, const Font@ ft = null) { const ResourceType@ type = r.type; //Origin underlay if(r.origin is drawFrom && r.origin !is null) { spritesheet::ResourceIconsMods.draw(0, pos); //Rarity if(type.rarity > 0) spritesheet::ResourceIconsMods.draw(6+type.rarity, pos); } drawResource(r.type, pos); if(r.origin is null || r.origin.owner is playerEmpire) { //Disabled overlay if(!r.usable) spritesheet::ResourceIconsMods.draw(2, pos); } //Export tick if(r.origin is drawFrom && r.exportedTo !is null && r.origin !is null && drawFrom.visible) spritesheet::ResourceIconsMods.draw(1, pos); //Vanish timer if(r.type.vanishMode != VM_Never && drawFrom !is null) { double timeLeft = r.type.vanishTime - r.vanishTime; if(r.exportedTo !is null) timeLeft /= r.exportedTo.resourceVanishRate; else if(r.origin !is null) timeLeft /= r.origin.resourceVanishRate; font::DroidSans_11_Bold.draw(pos.topLeft+vec2i(4, 4), formatShortTime(timeLeft), Color(0xffbb00ff)); } } void drawSmallResource(const ResourceType@ type, const Resource@ r, const recti& pos, Object@ drawFrom = null, bool onPlanet = false) { if(r !is null) { //Origin underlay if(r.origin is drawFrom && r.origin !is null && !onPlanet) { spritesheet::ResourceIconsSmallMods.draw(0, pos.padded(-2)); //Rarity if(type.rarity > 0) spritesheet::ResourceIconsSmallMods.draw(10, pos.padded(-2), getResourceRarityColor(type.rarity)); } if(drawFrom !is null && !r.usable && r.origin is null) { if(r.type.cls is foodClass) { FOOD_REQ.draw(pos); } else if(r.type.cls is waterClass) { WATER_REQ.draw(pos); } else if(r.type.level < LEVEL_REQ.length) { LEVEL_REQ[r.type.level].draw(pos); } else { UNKNOWN_REQ.draw(pos); } } else { type.smallIcon.draw(pos); } bool lowPop = false; if(drawFrom !is null) { if(!r.usable) { if(r.origin is null) { //Queued auto-import spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-2), Color(0xf800ffff)); } else if(drawFrom.owner is playerEmpire || drawFrom !is r.origin) { if(r.origin.owner !is playerEmpire) { //Queued import spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-2), Color(0xffe400ff)); } else { if(r.origin.hasSurfaceComponent && r.origin.resourceLevel >= r.type.level && r.origin.population < getPlanetLevelRequiredPop(r.origin, r.type.level)) { //Insufficient population spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-4), Color(0xff6300ff)); lowPop = true; } else { //Disabled spritesheet::ResourceIconsSmallMods.draw(2, pos.padded(-4)); } } } } else if(r.origin is drawFrom && r.origin.hasSurfaceComponent) { lowPop = r.origin.resourceLevel > r.origin.level; if(lowPop && !r.type.artificial) { //Insufficient population spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-4), Color(0xff6300ff)); } } } //Resource level icon if(type.level > 0 && type.level <= 3) spritesheet::ResourceIconsSmallMods.draw(3+type.level, pos.padded(-2)); //Resource class icon else if(type.cls is foodClass) spritesheet::ResourceIconsSmallMods.draw(7, pos.padded(-2)); else if(type.cls is scalableClass) spritesheet::ResourceIconsSmallMods.draw(16, pos.padded(-2)); //Export tick if(r.origin !is null && r.origin is drawFrom && r.exportedTo !is null && drawFrom.visible) spritesheet::ResourceIconsSmallMods.draw(1, pos.padded(-2)); //Excess tick if(r.exportedTo is null && r.origin !is null && r.usable && !lowPop) { if(r.origin.owner is playerEmpire && r.origin.hasSurfaceComponent && !r.type.isMaterial(r.origin.level) && r.origin.exportEnabled) spritesheet::ResourceIconsSmallMods.draw(3, pos.padded(-2)); } //Lock icon if(drawFrom is null || r.origin is drawFrom) { if(r.locked) spritesheet::ResourceIconsSmallMods.draw(15, pos.padded(-2)); else if(type.willLock) spritesheet::ResourceIconsSmallMods.draw(14, pos.padded(-2)); } } else { type.smallIcon.draw(pos); //Resource level icon if(type.level > 0 && type.level <= 3) spritesheet::ResourceIconsSmallMods.draw(3+type.level, pos.padded(-2)); //Resource class icon else if(type.cls is foodClass) spritesheet::ResourceIconsSmallMods.draw(7, pos.padded(-2)); else if(type.cls is scalableClass) spritesheet::ResourceIconsSmallMods.draw(16, pos.padded(-2)); //Lock icon if(type.willLock) spritesheet::ResourceIconsSmallMods.draw(14, pos.padded(-2)); } } class GuiResourceRequirements : BaseGuiElement { GuiText@[] text; GuiResources@[] resources; GuiResourceRequirements(IGuiElement@ ParentElement, Alignment@ align) { super(ParentElement, align); } GuiResourceRequirements(IGuiElement@ ParentElement, const recti& pos) { super(ParentElement, pos); } void set(const ResourceRequirements@ reqs, const Resources@ available = null, bool allowUniversal = true) { //Do calculations array@ remaining = null; if(available !is null) { @remaining = array(); reqs.satisfiedBy(available, null, allowUniversal, remaining); } //Remove previous for(uint i = 0, cnt = resources.length; i < cnt; ++i) resources[i].remove(); resources.length = 0; for(uint i = 0, cnt = text.length; i < cnt; ++i) text[i].remove(); text.length = 0; //Add all groups uint rCnt = reqs.reqs.length; int x = 0; int rsize = size.height - 15; for(uint i = 0; i < rCnt; ++i) { const ResourceRequirement@ req = reqs.reqs[i]; int amount = req.amount; if(remaining !is null) amount = remaining[i]; if(amount == 0) continue; GuiResources r(this, recti()); string caption; //Find which resources and caption to display switch(req.type) { case RRT_Resource: r.types.insertLast(req.resource); caption = toString(amount)+"x"; break; case RRT_Class: for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) { const ResourceType@ restype = req.cls.types[i]; if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay) continue; r.types.insertLast(restype); } if(r.types.length == 1) caption = toString(amount)+"x"; else caption = format(locale::RESOURCES_OF, toString(amount)); break; case RRT_Class_Types: for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) { const ResourceType@ restype = req.cls.types[i]; if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay) continue; if(available !is null && available.getAmount(restype) != 0) continue; r.types.insertLast(restype); } if(r.types.length == 1) caption = toString(amount)+"x"; else caption = format(locale::TYPES_OF, toString(amount)); break; case RRT_Level: for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { const ResourceType@ res = getResource(i); if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay) continue; if(res.level == req.level) r.types.insertLast(res); } if(r.types.length == 1) caption = toString(amount)+"x"; else caption = format(locale::RESOURCES_OF, toString(amount)); break; case RRT_Level_Types: for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { const ResourceType@ res = getResource(i); if(res.level != req.level) continue; if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay) continue; if(available !is null && available.getAmount(res) != 0) continue; r.types.insertLast(res); } if(r.types.length == 1) caption = toString(amount)+"x"; else caption = format(locale::TYPES_OF, toString(amount)); break; } if(r.types.length == 0) { r.remove(); continue; } //Scrunch icons if multiple int w = 0; if(r.types.length == 1) w = rsize; else w = (float(rsize) * 0.9f) * float(r.types.length); //Create the caption GuiText ctext(this, Alignment(Left+x, Top, Left+x+w, Top+15)); ctext.horizAlign = 0.5; ctext.font = FT_Small; ctext.text = caption; text.insertLast(ctext); //Position the resources element r.typeMode = true; @r.alignment = Alignment(Left+x, Top+15, Left+x+w, Bottom); resources.insertLast(r); x += w+16; } updateAbsolutePosition(); } void draw() { BaseGuiElement::draw(); } }; class GuiResources : BaseGuiElement { Object@ drawFrom; array types; Resource[] resources; bool typeMode = false; bool vertical = false; bool shrink = false; double horizAlign = 0.5; double vertAlign = 0.5; int spacing = 0; int hovered = -1; GuiResources(IGuiElement@ ParentElement, Alignment@ align) { super(ParentElement, align); _GuiResources(); } GuiResources(IGuiElement@ ParentElement, const recti& pos) { super(ParentElement, pos); _GuiResources(); } void _GuiResources() { MarkupTooltip tt(350, 0.f, true, true); tt.Lazy = true; tt.LazyUpdate = false; tt.Padding = 4; @tooltipObject = tt; } uint get_length() { if(typeMode) return types.length; else return resources.length; } string get_tooltip() { if(hovered < 0 || hovered >= int(length)) return ""; Resource@ r; const ResourceType@ type; if(typeMode) { @type = types[hovered]; } else { @r = resources[hovered]; @type = r.type; } return getResourceTooltip(type, r, drawFrom); } void calcOffset(vec2i& pos, vec2i& offset, vec2i& rsize) { uint cnt = length; uint isize = min(size.width, size.height); rsize = vec2i(isize, isize); pos = vec2i(); offset = vec2i(); if(vertical) { pos.x = double(size.width - isize) * horizAlign; int tot = (isize + spacing) * cnt - spacing; if(tot < size.height) { pos.y = double(size.height - tot) * vertAlign; offset.y = isize + spacing; } else { if(shrink) { isize = double(size.height) / double(cnt); rsize.x = isize; rsize.y = isize; offset.y = isize; } else if(cnt > 1) { offset.y = double(size.height - isize) / double(cnt - 1); } else { offset.y = isize; } } } else { pos.y = double(size.height - isize) * vertAlign; int tot = (isize + spacing) * cnt - spacing; if(tot < size.width) { pos.x = double(size.width - tot) * horizAlign; offset.x = isize + spacing; } else { if(shrink) { isize = double(size.width) / double(cnt); rsize.x = isize; rsize.y = isize; offset.x = isize; } else if(cnt > 1) { offset.x = double(size.width - isize) / double(cnt - 1); } else { offset.x = isize; } } } } bool onGuiEvent(const GuiEvent& event) { if(event.caller is this) { switch(event.type) { case GUI_Mouse_Left: hovered = -1; break; } } return BaseGuiElement::onGuiEvent(event); } bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) { if(source is this) { switch(event.type) { case MET_Moved: { int prevHovered = hovered; hovered = getOffsetItem(mousePos - AbsolutePosition.topLeft); if(prevHovered != hovered) tooltipObject.update(skin, this); } break; case MET_Button_Down: if(hovered != -1) return true; break; case MET_Button_Up: if(hovered != -1) { if(event.button == 2 && uint(hovered) < resources.length) zoomTo(resources[hovered].origin); emitClicked(event.button); return true; } break; } } return BaseGuiElement::onMouseEvent(event, source); } int getOffsetItem(const vec2i& off) { vec2i pos, ioff, rsize, offset = off; calcOffset(pos, ioff, rsize); offset -= pos; if(offset.x < 0 || offset.y < 0) return -1; int num = 0; if(vertical) { num = floor(double(offset.y) / double(ioff.y)); if(offset.y - (num * ioff.y) > rsize.y) return -1; } else { num = floor(double(offset.x) / double(ioff.x)); if(offset.y - (num * ioff.y) > rsize.y) return -1; } if(num >= int(length)) return -1; return num; } void draw() { vec2i pos, offset, rsize; calcOffset(pos, offset, rsize); vec2i hovPos; const Font@ ft = skin.getFont(FT_Normal); if(rsize.width < 32) @ft = skin.getFont(FT_Small); //Draw everything but the hovered resource for(uint i = 0, cnt = length; i < cnt; ++i) { if(hovered != int(i)) { if(typeMode) drawResource(types[i], recti_area(pos + AbsolutePosition.topLeft, rsize)); else drawResource(resources[i], recti_area(pos + AbsolutePosition.topLeft, rsize), drawFrom, ft); } else hovPos = pos; pos += offset; } //Draw hovered last so it's on top if(hovered != -1 && uint(hovered) < this.length) { if(typeMode) drawResource(types[hovered], recti_area(hovPos + AbsolutePosition.topLeft, rsize)); else drawResource(resources[hovered], recti_area(hovPos + AbsolutePosition.topLeft, rsize), drawFrom, ft); } BaseGuiElement::draw(); } }; class GuiResourceGrid : GuiIconGrid { Object@ drawFrom; array types; Resource[] resources; bool typeMode = false; bool singleMode = false; GuiResourceGrid(IGuiElement@ ParentElement, Alignment@ align) { super(ParentElement, align); _GuiResourceGrid(); } GuiResourceGrid(IGuiElement@ ParentElement, const recti& pos) { super(ParentElement, pos); _GuiResourceGrid(); } void _GuiResourceGrid() { iconSize = vec2i(21, 21); spacing = vec2i(5, 5); MarkupTooltip tt(350, 0.f, true, true); tt.Lazy = true; tt.LazyUpdate = false; tt.Padding = 4; @tooltipObject = tt; } uint get_length() override { if(typeMode) return types.length; else return resources.length; } void setSingleMode(bool mode = true, double align = 0.5) { singleMode = mode && length == 1; forceHover = singleMode; horizAlign = singleMode ? 0.0 : align; } bool onGuiEvent(const GuiEvent& event) override { if(event.caller is this) { switch(event.type) { case GUI_Clicked: if(uint(hovered) < resources.length) { if(event.value == 2) { zoomTo(resources[hovered].origin); return true; } } break; } } return BaseGuiElement::onGuiEvent(event); } string get_tooltip() override { if(hovered < 0 || hovered >= int(length)) return ""; Resource@ r; const ResourceType@ type; if(typeMode) { @type = types[hovered]; } else { @r = resources[hovered]; @type = r.type; } return getResourceTooltip(type, r, drawFrom); } void drawElement(uint i, const recti& pos) override { clearClip(); if(typeMode) drawSmallResource(types[i], null, pos, drawFrom); else drawSmallResource(resources[i].type, resources[i], pos, drawFrom); } void draw() override { GuiIconGrid::draw(); if(singleMode && length == 1) { Resource@ r; const ResourceType@ type; if(typeMode) { @type = types[0]; } else { @r = resources[0]; @type = r.type; } if(typeMode || drawFrom is null || r.origin is drawFrom) { uint affCnt = type.affinities.length; Color textColor; if(!typeMode && !r.usable && r.origin.owner is playerEmpire) { if(r.exportedTo !is null) { float pct = abs((frameTime % 1.0) - 0.5f) * 2.f; textColor = colors::Red.interpolate(colors::Orange, pct); } else { textColor = colors::Red; } } const Font@ ft = skin.getFont(FT_Bold); recti pos = AbsolutePosition.padded(34, 0, 0, 0); ft.draw(text=type.name, ellipsis=locale::ELLIPSIS, pos=pos.padded(0, 0, affCnt*28, 0), vertAlign=0.5, color=textColor); int x = 28; for(uint i = 0; i < affCnt; ++i) { uint aff = type.affinities[i]; getAffinitySprite(aff).draw(recti_area(vec2i(pos.botRight.x-x, pos.topLeft.y), vec2i(24, 24))); x += 28; } } } } }; const Sprite FOOD_REQ(spritesheet::ResourceClassIcons, 3); const Sprite WATER_REQ(spritesheet::ResourceClassIcons, 4); const Sprite UNKNOWN_REQ(spritesheet::ResourceClassIcons, 5); const Sprite[] LEVEL_REQ = { Sprite(), Sprite(spritesheet::ResourceClassIcons, 0), Sprite(spritesheet::ResourceClassIcons, 1), Sprite(spritesheet::ResourceClassIcons, 2) }; Sprite getAutoImportIcon(const AutoImportDesc& desc) { if(desc.type !is null) { if(desc.type is waterType) return WATER_REQ; return desc.type.smallIcon; } else if(desc.cls !is null) { if(desc.cls is foodClass) return FOOD_REQ; else if(desc.cls is waterClass) return WATER_REQ; return Sprite(); } else if(desc.level != -1 && uint(desc.level) < LEVEL_REQ.length) { return LEVEL_REQ[desc.level]; } return UNKNOWN_REQ; } Sprite getRequirementIcon(const ResourceRequirement@ req) { switch(req.type) { case RRT_Resource: if(req.resource is waterType) return WATER_REQ; else return req.resource.smallIcon; case RRT_Class: case RRT_Class_Types: if(req.cls is foodClass) return FOOD_REQ; else if(req.cls is waterClass) return WATER_REQ; else return UNKNOWN_REQ; case RRT_Level: case RRT_Level_Types: if(req.level < LEVEL_REQ.length) return LEVEL_REQ[req.level]; else return UNKNOWN_REQ; } return Sprite(); } class GuiResourceReqGrid : GuiIconGrid { Sprite[] sprites; string[] tooltips; GuiResourceReqGrid(IGuiElement@ ParentElement, Alignment@ align) { super(ParentElement, align); MarkupTooltip tt(350, 0.f, true, true); tt.Lazy = true; tt.LazyUpdate = false; tt.Padding = 4; @tooltipObject = tt; iconSize = vec2i(26,26); padding = vec2i(-2); spacing = vec2i(1); noClip = true; } GuiResourceReqGrid(IGuiElement@ ParentElement, const recti& pos) { super(ParentElement, pos); MarkupTooltip tt(350, 0.f, true, true); tt.Lazy = true; tt.LazyUpdate = false; tt.Padding = 4; @tooltipObject = tt; iconSize = vec2i(26,26); padding = vec2i(-2); spacing = vec2i(1); noClip = true; } string get_tooltip() override { if(hovered < 0 || uint(hovered) >= tooltips.length) return ""; return tooltips[hovered]; } uint get_length() override { return sprites.length; } void drawElement(uint i, const recti& pos) override { sprites[i].draw(pos.padded(-2)); } void set(const ResourceRequirements@ reqs, const Resources@ available = null, bool allowUniversal = true) { //Do calculations array@ remaining = null; if(available !is null) { @remaining = array(); reqs.satisfiedBy(available, null, allowUniversal, remaining); } sprites.length = 0; tooltips.length = 0; //Add all groups uint rCnt = reqs.reqs.length; for(uint i = 0; i < rCnt; ++i) { const ResourceRequirement@ req = reqs.reqs[i]; int amount = req.amount; if(remaining !is null) amount = remaining[i]; if(amount == 0) continue; //Find which icon to display switch(req.type) { case RRT_Resource: for(int i = 0; i < amount; ++i) { if(req.resource is waterType) sprites.insertLast(WATER_REQ); else sprites.insertLast(req.resource.smallIcon); tooltips.insertLast(req.resource.name); } break; case RRT_Class: { Sprite sprt; if(req.cls is foodClass) sprt = FOOD_REQ; else if(req.cls is waterClass) sprt = WATER_REQ; else sprt = UNKNOWN_REQ; string tip = format(locale::REQ_TYPE, req.cls.name, getSpriteDesc(sprt))+"\n"; for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) { const ResourceType@ restype = req.cls.types[i]; if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay) continue; if(restype.frequency == 0.0 || restype.rarity >= RR_Rare) continue; tip += format("[img=$1;28/]", getSpriteDesc(restype.smallIcon)); } for(int i = 0; i < amount; ++i) { sprites.insertLast(sprt); tooltips.insertLast(tip); } } break; case RRT_Class_Types: { Sprite sprt; if(req.cls is foodClass) sprt = FOOD_REQ; else if(req.cls is waterClass) sprt = WATER_REQ; else sprt = UNKNOWN_REQ; string tip = format(locale::REQ_TYPE_UNIQUE, req.cls.name, getSpriteDesc(sprt))+"\n"; for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) { const ResourceType@ restype = req.cls.types[i]; if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay) continue; if(restype.frequency == 0.0 || restype.rarity >= RR_Rare) continue; if(available !is null && available.getAmount(restype) != 0) continue; tip += format("[img=$1;28/]", getSpriteDesc(restype.smallIcon)); } for(int i = 0; i < amount; ++i) { sprites.insertLast(sprt); tooltips.insertLast(tip); } } break; case RRT_Level: { Sprite sprt; if(req.level < LEVEL_REQ.length) sprt = LEVEL_REQ[req.level]; else sprt = UNKNOWN_REQ; string tip = format(locale::REQ_LEVEL, toString(req.level), getSpriteDesc(sprt))+"\n"; for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { const ResourceType@ res = getResource(i); if(res.level != req.level) continue; if(res.frequency == 0.0 || res.rarity >= RR_Rare) continue; if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay) continue; tip += format("[img=$1;28/]", getSpriteDesc(res.smallIcon)); } for(int i = 0; i < amount; ++i) { sprites.insertLast(sprt); tooltips.insertLast(tip); } } break; case RRT_Level_Types: { Sprite sprt; if(req.level < LEVEL_REQ.length) sprt = LEVEL_REQ[req.level]; else sprt = UNKNOWN_REQ; string tip = format(locale::REQ_LEVEL_UNIQUE, toString(req.level), getSpriteDesc(sprt))+"\n"; for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { const ResourceType@ res = getResource(i); if(res.level != req.level) continue; if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay) continue; if(res.frequency == 0.0 || res.rarity >= RR_Rare) continue; if(available !is null && available.getAmount(res) != 0) continue; tip += format("[img=$1;28/]", getSpriteDesc(res.smallIcon)); } for(int i = 0; i < amount; ++i) { sprites.insertLast(sprt); tooltips.insertLast(tip); } } break; } } updateAbsolutePosition(); } }; #section game import void zoomTo(Object@ obj) from "tabs.GalaxyTab"; #section menu void zoomTo(Object@ obj) {} #section all