#section game import elements.BaseGuiElement; import design_stats; import util.design_export; import elements.GuiSprite; import elements.GuiText; import elements.GuiMarkupText; import elements.GuiPanel; import elements.GuiSkinElement; import elements.MarkupTooltip; import util.formatting; from resources import getBuildCost; export GuiBlueprint; export GuiDownloadedBlueprint; export GuiBlueprintStats; /* {{{ Blueprints display */ enum HexSprite { HS_Hex = 0, HS_ElevEast = 1, HS_ElevSouth = 2, HS_ElevWest = 3, HS_Borders = 4, HS_Selection = 10, }; enum SysDrawMode { SDM_Static, SDM_Rotatable, }; const array BORDER_DRAW_ORDER = { HEX_Up, HEX_UpLeft, HEX_UpRight, HEX_DownLeft, HEX_DownRight, HEX_Down }; const double BORDER_HEIGHT = 4.0; const double BORDER_OFFSET = 8.0; const vec2i HEX_SIZE(126, 65); const double WIDTH_SCRUNCH = 92; const double HEIGHT_SCRUNCH = 33; const vec2d WALL_RATIO(253.0 / 253.0, 162.0 / 130.0); const Color DAMAGE_BASE(0x000000ff); const Color DAMAGE_GLOWING(0xff0000ff); const Color DAMAGE_TILE(0x444444ff); const Color DAMAGE_PIC(0x111111ff); const Color REPAIR_GLOW(0x00ff00ff); const double GLOW_PERIOD = 1.0; const Color ARC_CENTER_COLOR(0x00ff00ff); const Color ARC_BOUND_COLOR(0xff0000ff); class GuiBlueprint : BaseGuiElement { //Position of the start of the hex grid vec2i hexPos; //Real size of the hex grid vec2i gridSize; //Real size of a hexagon vec2i hexSize; //Hexagon size factor double hexFactor; //Hovered hexagon vec2i hexHovered(-1, -1); //Whether all hexagons should be used, //even ones marked inactive bool activateAll = false; //Whether to display empty hexagons bool displayEmpty = false; //Whether to display inactive hexagons bool displayInactive = false; //Whether to highlight the hovered hex bool displayHovered = true; //Whether to highlight exterior hexes bool displayExterior = false; //Whether to display the hull's model in the background bool displayHull = false; //Whether to pop out hovered hexes bool popHover = false; //Size of the popped out hex vec2i popSize(102,53); //Whether to show firing arcs bool hoverArcs = false; //Whether to show hull hex use weights bool displayHullWeights = false; //Alignment of blueprint into area double horizAlign = 0.5; double vertAlign = 0.5; //Necessary: Hull type to draw const Hull@ hull; //Optional: Current design const Design@ design; //Optional: Current blueprint const Blueprint@ bp; //Optional: Object of blueprint Object@ obj; //Current frame damage color float glowPct = 0.f; Color damColor; //Damage storage array prevDamage; array damagedAt; //Draw buffers array emptyFloors; array emptyColors; array hexFloors; array floorColors; GuiBlueprint(IGuiElement@ ParentElement, const recti& Rectangle) { super(ParentElement, Rectangle); } GuiBlueprint(IGuiElement@ ParentElement, Alignment@ align) { super(ParentElement, align); } void display(const Hull@ Hull) { @hull = Hull; @design = null; @bp = null; @obj = null; hexHovered = vec2i(-1, -1); calculate(); prevDamage.length = 0; damagedAt.length = 0; } void display(const Design@ dsg) { @hull = dsg.hull; @design = dsg; @bp = null; @obj = null; hexHovered = vec2i(-1, -1); calculate(); prevDamage.length = 0; damagedAt.length = 0; } void display(Ship@ ship) { @bp = ship.blueprint; if(bp !is null) @design = bp.design; else @design = null; if(design !is null) @hull = design.hull; else @hull = null; @obj = ship; hexHovered = vec2i(-1, -1); calculate(); ObjectLock lock(obj, true); prevDamage.length = design.usedHexCount; damagedAt.length = design.usedHexCount; for(uint i = 0; i < design.usedHexCount; ++i) { prevDamage[i] = bp.getHexStatus(i).hp; damagedAt[i] = 0.0; } } float hexDist(const vec2u& hex, const vec2i&in offset) { int seg = floor(BORDER_HEIGHT * hexFactor); vec2i pos = getHexPos(hex); int elev = getElevation(hex); pos.y -= elev * seg; return recti_area(pos, hexSize).center.distanceTo(offset); } vec2i getGridPosition(const vec2i& offset, bool selectInactive = false) { //Find the closest flat hex vec2d pos; pos.x = double(offset.x) / (floor(double(WIDTH_SCRUNCH) * hexFactor) / 0.75); pos.y = double(offset.y) / double(hexSize.y); vec2i hex = getHexGridPosition(pos); //Check all surrounding hexes float lowest = hexDist(vec2u(hex), offset); vec2i closest = hex; if(lowest != 0.f) { for(uint i = 0; i < 6; ++i) { vec2u other(hex); if(!advanceHexPosition(other, vec2u(hull.gridSize), HexGridAdjacency(i))) continue; float dist = hexDist(other, offset); if(dist < lowest) { lowest = dist; closest = vec2i(other); if(lowest == 0.f) break; } } } hex = closest; //Make sure the hex is active if(uint(hex.x) >= hull.active.width || uint(hex.y) >= hull.active.height) return vec2i(-1, -1); if(!selectInactive && !hull.active.get(hex.x, hex.y)) return vec2i(-1, -1); return hex; } bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) { if(source is this && hull !is null) { switch(event.type) { case MET_Moved: { vec2i mouse = vec2i(event.x, event.y); vec2i offset = mouse - hexPos - AbsolutePosition.topLeft; vec2i prev = hexHovered; hexHovered = getGridPosition(offset, activateAll); if(obj !is null && hexHovered != vec2i(-1,-1)) { ObjectLock lock(obj, true); if(!activateAll && bp !is null) { auto@ status = bp.getHexStatus(hexHovered.x, hexHovered.y); if(status !is null && status.flags & HF_Gone != 0) hexHovered = vec2i(-1, -1); } } if(prev.x != hexHovered.x || prev.y != hexHovered.y) { GuiEvent evt; evt.type = GUI_Hover_Changed; @evt.caller = this; Parent.onGuiEvent(evt); } } break; case MET_Button_Up: { if(hexHovered.x < 0 || hexHovered.y < 0) break; if(hexHovered.x >= hull.gridSize.width || hexHovered.y >= hull.gridSize.height) break; if(!activateAll && !hull.active.get(hexHovered.x, hexHovered.y)) break; GuiEvent evt; evt.type = GUI_Clicked; evt.value = event.button; @evt.caller = this; Parent.onGuiEvent(evt); } break; } } return BaseGuiElement::onMouseEvent(event, source); } bool onGuiEvent(const GuiEvent& event) { if(event.caller is this) { switch(event.type) { case GUI_Mouse_Entered: hexHovered = vec2i(-1,-1); return false; case GUI_Mouse_Left: if(hexHovered.x < 0 || hexHovered.y < 0) return false; hexHovered = vec2i(-1,-1); emitHoverChanged(); return false; } } return BaseGuiElement::onGuiEvent(event); } int getElevation(const vec2u& pos) { if(pos.x >= uint(hull.gridSize.x) || pos.y >= uint(hull.gridSize.y)) return -3; if(!hull.active[pos]) return -3; if(design !is null) { const Subsystem@ sys = design.subsystem(pos); if(sys !is null) return sys.type.elevation; } return 0; } void drawElevation(const vec2u& hex, vec2i& pos, double hexFactor, const vec2i& hexSize, bool floating = false, bool second=true) { int seg = floor(BORDER_HEIGHT * hexFactor); //Find surrounding elevation int elev = getElevation(hex); int s_elev = 0; int se_elev = 0, sw_elev = 0; if(!floating) { s_elev = getElevation(vec2u(hex.x, hex.y+1)); if(hex.x % 2 == 0) { se_elev = getElevation(vec2u(hex.x+1, hex.y)); sw_elev = getElevation(vec2u(hex.x-1, hex.y)); } else { se_elev = getElevation(vec2u(hex.x+1, hex.y+1)); sw_elev = getElevation(vec2u(hex.x-1, hex.y+1)); } } else { elev += 1; } //Draw the borders int cur = min(s_elev, min(se_elev, sw_elev)); if(elev < cur) { pos.y -= elev * seg; return; } pos.y -= cur * seg; recti wallPos = recti_centered(pos + vec2i(hexSize.x / 2, hexSize.y / 2 - BORDER_OFFSET * hexFactor), vec2i(hexSize.x * WALL_RATIO.x, hexSize.y * WALL_RATIO.y)); while(cur < elev) { if(elev != -3) { if(sw_elev <= cur && (second ? sw_elev >= 0 : sw_elev < 0)) spritesheet::HexRiser.draw(0, wallPos); if(s_elev <= cur && (second ? s_elev >= 0 : s_elev < 0)) spritesheet::HexRiser.draw(1, wallPos); if(se_elev <= cur && (second ? se_elev >= 0 : se_elev < 0)) spritesheet::HexRiser.draw(2, wallPos); } cur += 1; pos.y -= seg; wallPos -= vec2i(0, seg); } } vec2i getHexPos(const vec2u& hex, bool applyElevation = false) { vec2i pos; pos.x += hex.x * floor(WIDTH_SCRUNCH * hexFactor); pos.y += hex.y * hexSize.y; if(hex.x % 2 != 0) pos.y += ceil(HEIGHT_SCRUNCH * hexFactor); if(applyElevation) pos.y -= getElevation(hex) * floor(BORDER_HEIGHT * hexFactor); return pos; } void drawHexFloor(const vec2u& hex, const vec2i& atPos, const vec2i& size, bool floating = false) { bool active = hull.active[hex]; if(!displayInactive && !active) return; uint hexIndex = 0; const HexStatus@ status; if(bp !is null) { hexIndex = uint(design.hexStatusIndex(hex)); @status = bp.getHexStatus(hexIndex); if(status is null || status.flags & HF_Gone != 0) return; } vec2i pos = atPos; double factor = 0; if(floating) factor = double(size.x) / double(HEX_SIZE.x); else factor = hexFactor; const Subsystem@ subsys = null; if(design !is null) @subsys = design.subsystem(hex); if(subsys !is null && subsys.type.hasTag(ST_NoFloor)) return; //Draw the elevation border drawElevation(hex, pos, factor, size, second=false); //Draw the actual hexagon Color color(0xffffffff); const Subsystem@ sys = null; if(design !is null) @sys = design.subsystem(hex); if(sys !is null) color = sys.type.color; else if(!active) color = Color(0x4040404f); else if(displayExterior && hull.isExterior(hex)) color = Color(0xccccccff); //Display health float health = 0.f; bool inactive = false, repairing = false, damaging = false; if(bp !is null) { if(hexIndex != uint(-1)) { const SysStatus@ sys = bp.getSysStatus(hex.x, hex.y); if(sys !is null) { if(sys.status != ES_Active) { inactive = true; color = Color(0x909090ff).interpolate(color, 0.3f); } } if(status !is null && status.flags & HF_NoHP == 0) { repairing = int(hex.x) == bp.repairingHex.x && int(hex.y) == bp.repairingHex.y; if(prevDamage[hexIndex] > status.hp) { damagedAt[hexIndex] = frameTime; damaging = true; prevDamage[hexIndex] = status.hp; } else { damaging = damagedAt[hexIndex] > frameTime - 3.0; } health = status.hp / float(0xff); if(repairing && health > 0.01f) color = DAMAGE_GLOWING.interpolate(color, health); else if(health < 0.01f) color = DAMAGE_TILE; else if(damaging) color = damColor.interpolate(color, health); else color = DAMAGE_TILE.interpolate(color, health); if(repairing) color = color.interpolate(REPAIR_GLOW, 1.f-glowPct); } } } recti drawPos = recti_area(pos, size); if(sys is null) { emptyFloors.insertLast(drawPos); emptyColors.insertLast(color); } else { hexFloors.insertLast(drawPos); floorColors.insertLast(color); } /*if(sys is null)*/ /* material::HexEmpty.draw(drawPos, color);*/ /*else*/ /* material::HexFloor.draw(drawPos, color);*/ } void drawHexWalls(const vec2u& hex, const vec2i& atPos, const vec2i& size, bool second = false) { bool active = hull.active[hex]; if(!active) return; const Subsystem@ sys = null; if(design !is null) @sys = design.subsystem(hex); if(sys is null) return; if(sys.type.hasTag(ST_IsArmor) || sys.type.hasTag(ST_NoWall)) return; bool isCore = sys !is null && hex == sys.core; bool noWallBack = false; bool noWallFront = false; if(sys !is null) { const ModuleDef@ mod = design.module(hex); if(isCore) { if(sys.type.hasTag(ST_NoBackWall)) noWallBack = true; if(sys.type.hasTag(ST_NoFrontWall)) noWallFront = true; } else if(mod !is sys.type.defaultModule && mod !is sys.type.coreModule && mod.defaultUnlock) { if(sys.type.hasTag(ST_ModuleNoFrontWall)) noWallFront = true; } } double turretRadians = INFINITY; if(isCore && sys.type.hasTag(ST_Rotatable) && sys.effectorCount != 0) { vec2d dir(sys.direction.x, -sys.direction.z); turretRadians = dir.radians(); } //Get the color of the wall Color color(0xffffffff); if(sys !is null) color = sys.type.color; else if(!active) color = Color(0x4040404f); else if(displayExterior && hull.isExterior(hex)) color = Color(0xccccccff); float health = 1.f; bool inactive = false, repairing = false, damaging = false; uint hexIndex = 0; const HexStatus@ status; if(bp !is null) { hexIndex = uint(design.hexStatusIndex(hex)); @status = bp.getHexStatus(hexIndex); if(status !is null) { const SysStatus@ sysstatus = bp.getSysStatus(hex.x, hex.y); if(sysstatus !is null) { if(sysstatus.status != ES_Active) { inactive = true; color = Color(0x909090ff).interpolate(color, 0.3f); } } if(status !is null && status.flags & HF_NoHP == 0) { repairing = int(hex.x) == bp.repairingHex.x && int(hex.y) == bp.repairingHex.y; if(prevDamage[hexIndex] > status.hp) { damagedAt[hexIndex] = frameTime; damaging = true; prevDamage[hexIndex] = status.hp; } else { damaging = damagedAt[hexIndex] > frameTime - 3.0; } health = status.hp / float(0xff); if(repairing && health > 0.01f) color = DAMAGE_GLOWING.interpolate(color, health); else if(health < 0.01f) color = DAMAGE_TILE; else if(damaging) color = damColor.interpolate(color, health); else color = DAMAGE_TILE.interpolate(color, health); if(repairing) color = color.interpolate(REPAIR_GLOW, 1.f-glowPct); } } } vec2i pos = atPos; pos.y -= getElevation(hex) * floor(BORDER_HEIGHT * hexFactor); //Draw the subsystem borders vec2u grid(design.hull.gridSize); recti drawPos = recti_area(pos, size); recti wallPos = recti_centered(drawPos.center, vec2i(size.x * WALL_RATIO.x, size.y * WALL_RATIO.y)); uint startAt = 0; uint endAt = 3; if(second) { startAt = 3; endAt = 6; } for(uint i = startAt; i < endAt; ++i) { uint edge = BORDER_DRAW_ORDER[i]; vec2u other = hex; const Subsystem@ otherSys = null; if(advanceHexPosition(other, grid, HexGridAdjacency(edge))) @otherSys = design.subsystem(other); if(otherSys is null) { if(turretRadians != INFINITY && abs(angleDiff(turretRadians, hexToRadians(HexGridAdjacency(edge)))) < pi / 3.0) continue; if(noWallBack && (edge == HEX_UpLeft || edge == HEX_DownLeft)) continue; if(noWallFront && (edge == HEX_UpRight || edge == HEX_DownRight)) continue; } if(sys !is otherSys) spritesheet::HexWall.draw(edge, wallPos, color); } } void drawHex(const vec2u& hex, const vec2i& atPos, const vec2i& size, bool floating = false, bool drawFloor = true, bool drawWalls = true) { bool active = hull.active[hex]; if(!displayInactive && !active) return; if(drawFloor) drawHexFloor(hex, atPos, size, floating); uint hexIndex = 0; const HexStatus@ status; if(bp !is null) { hexIndex = uint(design.hexStatusIndex(hex)); @status = bp.getHexStatus(hexIndex); if(status.flags & HF_Gone != 0) return; } vec2i pos = atPos; double factor = 0; if(floating) factor = double(size.x) / double(HEX_SIZE.x); else factor = hexFactor; /*drawElevation(hex, pos, factor, size, floating, second=true);*/ pos.y -= getElevation(hex) * floor(BORDER_HEIGHT * hexFactor); const Subsystem@ sys = null; if(design !is null) @sys = design.subsystem(hex); recti drawPos = recti_area(pos, size); bool isCore = sys !is null && hex == sys.core; drawWalls = drawWalls && (sys is null || (!sys.type.hasTag(ST_IsArmor) && !sys.type.hasTag(ST_NoWall))); bool noWallBack = sys !is null && isCore && sys.type.hasTag(ST_NoBackWall); double turretRadians = INFINITY; Color color(0xffffffff); if(sys !is null) color = sys.type.color; else if(!active) color = Color(0x4040404f); else if(displayExterior && hull.isExterior(hex)) color = Color(0xccccccff); float health = 1.f; bool inactive = false, repairing = false, damaging = false; if(bp !is null) { if(hexIndex != uint(-1)) { const SysStatus@ sysstatus = bp.getSysStatus(hex.x, hex.y); if(sysstatus !is null) { if(sysstatus.status != ES_Active) { inactive = true; color = Color(0x909090ff).interpolate(color, 0.3f); } } if(status !is null && status.flags & HF_NoHP == 0) { repairing = int(hex.x) == bp.repairingHex.x && int(hex.y) == bp.repairingHex.y; if(prevDamage[hexIndex] > status.hp) { damagedAt[hexIndex] = frameTime; damaging = true; prevDamage[hexIndex] = status.hp; } else { damaging = damagedAt[hexIndex] > frameTime - 3.0; } health = status.hp / float(0xff); if(repairing && health > 0.01f) color = DAMAGE_GLOWING.interpolate(color, health); else if(health < 0.01f) color = DAMAGE_TILE; else if(damaging) color = damColor.interpolate(color, health); else color = DAMAGE_TILE.interpolate(color, health); if(repairing) color = color.interpolate(REPAIR_GLOW, 1.f-glowPct); } } } if(sys !is null && isCore && sys.type.hasTag(ST_Rotatable) && sys.effectorCount != 0) { vec2d dir(sys.direction.x, -sys.direction.z); turretRadians = dir.radians(); } //Draw the subsystem borders if(sys !is null && drawWalls) { vec2u grid(design.hull.gridSize); recti wallPos = recti_centered(drawPos.center, vec2i(size.x * WALL_RATIO.x, size.y * WALL_RATIO.y)); for(uint i = 0; i < 3; ++i) { uint edge = BORDER_DRAW_ORDER[i]; vec2u other = hex; const Subsystem@ otherSys = null; if(advanceHexPosition(other, grid, HexGridAdjacency(edge))) @otherSys = design.subsystem(other); if(otherSys is null) { if(turretRadians != INFINITY && abs(angleDiff(turretRadians, hexToRadians(HexGridAdjacency(edge)))) < pi / 3.0) continue; if(noWallBack && (edge == HEX_UpLeft || edge == HEX_DownLeft)) continue; } if(sys !is otherSys) spritesheet::HexWall.draw(edge, wallPos, color); } } //Draw the picture if(design !is null) { const ModuleDef@ mod = design.module(hex); Color color; if(bp !is null) { if(inactive) color = Color(0x606060ff).interpolate(color, 0.5f); if(repairing && health > 0.01f) color = DAMAGE_GLOWING.interpolate(color, health); else if(health < 0.01f) color = DAMAGE_PIC; else if(damaging) color = damColor.interpolate(color, health); else color = DAMAGE_PIC.interpolate(color, health); if(repairing) color = color.interpolate(REPAIR_GLOW, 1.f-glowPct); } if(mod !is null && mod.sprite.valid) { //Modify size to draw size vec2i modSize = mod.sprite.size; modSize.x = double(modSize.x) * factor; modSize.y = double(modSize.y) * factor; //Offset for floating pos.x -= (modSize.x - size.x) / 2; if(modSize.y > size.y) pos.y -= (modSize.y - size.y); else pos.y -= (modSize.y - size.y) / 2; double rotation = 0.0; if(mod is sys.type.coreModule && sys.type.hasTag(ST_Rotatable)) { vec3d rot = sys.direction; vec2d rightvec(1.0, 0.0); vec2d rotvec(rot.x, rot.z); rotation = rightvec.getRotation(rotvec); } //Draw the image switch(mod.drawMode) { case SDM_Static: if(rotation != 0) mod.sprite.draw(recti_area(pos, modSize), color, rotation); else mod.sprite.draw(recti_area(pos, modSize), color); break; case SDM_Rotatable: { vec3d rot = sys.direction; vec2d rightvec(1.0, 0.0); vec2d rotvec(rot.x, -rot.z); rotation = rightvec.getRotation(rotvec); if(modSize.y > size.y) pos.y += (modSize.y - size.y) / 2; Sprite sprt = mod.sprite; sprt.index += HexGridAdjacency(radiansToHex(rotation)); double diffrot = hexToRadians(HexGridAdjacency(sprt.index)) - rotation; sprt.draw(recti_area(pos, modSize), color, diffrot); } break; } } } //Draw the subsystem borders recti wallPos = recti_centered(drawPos.center, vec2i(size.x * WALL_RATIO.x, size.y * WALL_RATIO.y)); if(sys !is null && drawWalls) { vec2u grid(design.hull.gridSize); for(uint i = 3; i < 6; ++i) { uint edge = BORDER_DRAW_ORDER[i]; vec2u other = hex; const Subsystem@ otherSys = null; if(advanceHexPosition(other, grid, HexGridAdjacency(edge))) @otherSys = design.subsystem(other); if(otherSys is null) { if(turretRadians != INFINITY && abs(angleDiff(turretRadians, hexToRadians(HexGridAdjacency(edge)))) < pi / 3.0) continue; if(noWallBack && (edge == HEX_UpLeft || edge == HEX_DownLeft)) continue; } if(sys !is otherSys) spritesheet::HexWall.draw(edge, wallPos, color); } } //Draw hex use weight if(displayHullWeights) { vec2d pctPos = getHexPosition(hex.x, hex.y); pctPos.x += 0.75 * 0.5; pctPos.y += 0.5; pctPos.x /= double(hull.gridSize.width) * 0.75; pctPos.y /= double(hull.gridSize.height); double w = hull.getMatchDistance(pctPos); skin.getFont(FT_Small).draw( pos=wallPos, horizAlign=0.5, vertAlign=0.5, stroke=colors::Black, text=toString(w, 0)); } //Draw hex errors if(design !is null && design.isErrorHex(hex)) material::HexError.draw(drawPos); } void updateAbsolutePosition() { BaseGuiElement::updateAbsolutePosition(); calculate(); } void calculate() { if(hull is null) return; //Sizes before zoom vec2i bSize = size; if(bSize.width == 0 || bSize.height == 0) return; double bAspect = double(bSize.width) / double(bSize.height); double hAspect = double(HEX_SIZE.width) / double(HEX_SIZE.height); vec2i grid = hull.gridSize; //Calculate necessary zoom double xneed = double(bSize.width) / (double(grid.width) * 0.75); double yneed = double(bSize.height) / double(grid.height) * hAspect; hexSize.x = floor(min(xneed, yneed)); hexSize.y = max(ceil(double(hexSize.x) / hAspect), 1.0); hexFactor = double(hexSize.x) / double(HEX_SIZE.x); gridSize.x = floor(WIDTH_SCRUNCH * hexFactor) * double(grid.width); gridSize.y = hexSize.y * double(grid.height); hexPos = vec2i( double(bSize.width - gridSize.x) * horizAlign, double(bSize.height - gridSize.y - floor(HEIGHT_SCRUNCH*hexFactor*0.5)) * vertAlign); } void draw() { if(hull is null) return; //Lock the object when displaying an //active blueprint so we can safely //pull data from its status members if(obj !is null) { ObjectLock lock(obj, true); drawAll(); } else { drawAll(); } } void drawPopped(vec2u hex, const vec2i& start) { bool active = hull.active[hex]; if(!displayInactive && !active) return; recti around = recti_area(getHexPos(hex) + start, hexSize); around = recti_centered(around.center, popSize); drawHex(hex, around.topLeft, around.size, true); } void drawFireArc(const vec2u& hex, float alpha = 1.f) { if(design is null) return; const Subsystem@ sys = design.subsystem(hex); const ModuleDef@ mod = design.module(hex); if(sys !is null && mod !is null && mod is sys.type.coreModule && sys.effectorCount != 0) { auto@ eff = sys.effectors[0]; if(eff.fireArc < pi || sys.type.hasTag(ST_HexLimitArc)) { vec3d dir = eff.turretAngle; vec2d flatDir(dir.x, dir.z); double rad = flatDir.radians(); double arc = eff.fireArc; Color color = sys.type.color; color.a *= alpha; recti pos = recti_centered(getHexPos(hex) + hexPos + AbsolutePosition.topLeft + (hexSize / 2), vec2i(hexSize.width * 7)); shader::MIN_RAD = rad - arc; shader::MAX_RAD = rad + arc; clearClip(); material::FireArc2D.draw(pos, color); } } } void drawAll() { emptyFloors.length = 0; emptyColors.length = 0; hexFloors.length = 0; floorColors.length = 0; if(hexSize.width == 0) calculate(); float glowTime = frameTime % GLOW_PERIOD; glowPct = 0.f; if(glowTime < GLOW_PERIOD * 0.5f) glowPct = glowTime / (GLOW_PERIOD * 0.5f); else glowPct = 1.f - ((glowTime - GLOW_PERIOD * 0.5f) / (GLOW_PERIOD * 0.5f)); damColor = DAMAGE_BASE.interpolate(DAMAGE_GLOWING, glowPct); //Draw the background vec2i topLeft = AbsolutePosition.topLeft; recti gridPos = recti_area(topLeft + hexPos, gridSize); if(displayHull) hull.model.draw(hull.background, gridPos, quaterniond_fromAxisAngle(vec3d_front(), pi * -0.5), hull.backgroundScale); //Draw all the hexes uint width = hull.gridSize.width; uint height = hull.gridSize.height; vec2i start = hexPos + topLeft; for(uint y = 0; y < height; ++y) { for(uint x = 0; x < width; x += 2) drawHexFloor(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize); for(uint x = 1; x < width; x += 2) drawHexFloor(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize); } for(uint i = 0, cnt = emptyFloors.length; i < cnt; ++i) material::HexEmpty.draw(emptyFloors[i], emptyColors[i]); for(uint i = 0, cnt = hexFloors.length; i < cnt; ++i) material::HexFloor.draw(hexFloors[i], floorColors[i]); /*for(uint y = 0; y < height; ++y) {*/ /* for(uint x = 0; x < width; x += 2)*/ /* drawHex(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, drawFloor=false, drawWalls=true);*/ /* for(uint x = 1; x < width; x += 2)*/ /* drawHex(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, drawFloor=false, drawWalls=true);*/ /*}*/ for(uint y = 0; y < height; ++y) { for(uint x = 0; x < width; x += 2) drawHexWalls(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize); for(uint x = 0; x < width; x += 2) drawHex(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, drawFloor=false, drawWalls=false); for(uint x = 0; x < width; x += 2) drawHexWalls(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, second=true); for(uint x = 1; x < width; x += 2) drawHexWalls(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize); for(uint x = 1; x < width; x += 2) drawHex(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, drawFloor=false, drawWalls=false); for(uint x = 1; x < width; x += 2) drawHexWalls(vec2u(x, y), getHexPos(vec2u(x, y)) + start, hexSize, second=true); } if(hexHovered.x >= 0 && hexHovered.y >= 0 && (design is null || design.validHex(vec2u(hexHovered)))) { //Draw popped hex if(popHover) { drawPopped(vec2u(hexHovered), start); } //Draw selection else if(displayHovered) { material::SubsystemCursor.draw(recti_centered( getHexPos(vec2u(hexHovered), true) + start + (hexSize / 2), vec2i(hexSize.x * WALL_RATIO.x, hexSize.y * WALL_RATIO.y))); } //Draw firing arcs when hovering turrets if(hoverArcs && design !is null) { vec2u hex = vec2u(hexHovered); drawFireArc(hex); } } BaseGuiElement::draw(); } }; /* }}} */ /* {{{ Blueprints from the net */ class DownloadedComment { string author; string ctime; string content; }; class GuiDownloadedBlueprint : BaseGuiElement { GuiBlueprint@ bp = GuiBlueprint(this, Alignment().fill()); DesignDescriptor desc; WebData data; int designId; const Design@ dsg; bool loaded = false; bool started = false; bool statsShown = false; recti padding; string author; string description; string ctime; bool hasUpvoted = false; uint upvotes = 0; bool isMine = false; uint commentCount = 0; array comments; GuiPanel@ statPanel; GuiSkinElement@ shipCostBG; GuiMarkupText@ shipCost; GuiBlueprintStats@ globalStats; GuiSkinElement@ statsPopup; GuiSkinElement@ sysNameBG; GuiText@ sysName; GuiSkinElement@ sysCostBG; GuiMarkupText@ sysCost; GuiBlueprintStats@ sysStats; GuiSkinElement@ hexNameBG; GuiText@ hexName; GuiBlueprintStats@ hexStats; GuiSkinElement@ hexCostBG; GuiMarkupText@ hexCost; GuiDownloadedBlueprint(IGuiElement@ elem, const recti& pos) { super(elem, pos); } GuiDownloadedBlueprint(IGuiElement@ elem, Alignment@ align) { super(elem, align); } void load(int id) { if(started && !loaded) return; clear(); started = true; designId = id; webAPICall("design/"+toString(id), this.data); } void update() { if(started && !loaded && data.completed && !data.error) { loaded = true; JSONTree tree; tree.parse(data.result); finishLoad(tree.root); } } void finishLoad(JSONNode@ root) { if(root is null || !root.isObject()) return; loaded = true; started = true; JSONNode@ data = root.findMember("data"); if(!data.isString()) return; JSONTree dsgTree; dsgTree.parse(data.getString()); if(unserialize_design(dsgTree, desc)) { @desc.hull = getBestHull(desc, getHullTypeTag(desc.hull)); @dsg = makeDesign(desc); bp.display(dsg); updateGlobalStats(); } @data = root.findMember("id"); if(data !is null && data.isInt()) designId = data.getInt(); @data = root.findMember("author"); if(data !is null && data.isString()) author = data.getString(); else author = "N/A"; @data = root.findMember("description"); if(data !is null && data.isString()) description = data.getString(); else description = ""; @data = root.findMember("ctime"); if(data !is null && data.isString()) ctime = data.getString(); else ctime = ""; @data = root.findMember("isMine"); if(data !is null && data.isBool()) isMine = data.getBool(); else isMine = false; @data = root.findMember("hasUpvoted"); if(data !is null && data.isBool()) hasUpvoted = data.getBool() || isMine; else hasUpvoted = isMine; @data = root.findMember("commentCount"); if(data !is null && data.isNumber()) commentCount = data.getNumber(); else commentCount = 0; @data = root.findMember("comments"); if(data !is null && data.isArray()) { comments.length = data.size(); for(uint i = 0, cnt = data.size(); i < cnt; ++i) { auto@ c = comments[i]; auto@ dat = data[i]; if(!dat.isObject()) continue; auto@ n = dat.findMember("author"); if(n !is null && n.isString()) c.author = n.getString(); @n = dat.findMember("ctime"); if(n !is null && n.isString()) c.ctime = n.getString(); @n = dat.findMember("content"); if(n !is null && n.isString()) c.content = n.getString(); } } else { comments.length = 0; } @data = root.findMember("upvotes"); if(data !is null && data.isNumber()) upvotes = data.getNumber(); else upvotes = 0; } void set_showStats(bool value) { if(statsShown == value) return; statsShown = value; if(statsShown) { if(statPanel is null) { @statPanel = GuiPanel(this, Alignment(Right-250, Top, Right, Bottom)); @shipCostBG = GuiSkinElement(statPanel, recti(0,4,246,38), SS_HorizBar); shipCostBG.color = Color(0x888888ff); shipCostBG.padding = recti(0,-4,0,-4); @shipCost = GuiMarkupText(shipCostBG, Alignment().padded(8,0,0,4)); shipCost.text = "--"; @globalStats = GuiBlueprintStats(statPanel, recti(vec2i(0, 30), vec2i(250, 230))); @statsPopup = GuiSkinElement(this, recti_area(0, 30, 250, 200), SS_PlainBox); statsPopup.visible = false; @sysNameBG = GuiSkinElement(statsPopup, recti(0, 0, 246, 30), SS_SubTitle); @sysName = GuiText(sysNameBG, recti(12, 0, 250, 30)); sysName.font = FT_Medium; @sysCostBG = GuiSkinElement(statsPopup, recti(0,0,246,30), SS_HorizBar); sysCostBG.color = Color(0x888888ff); sysCostBG.padding = recti(0,-4,0,-4); @sysCost = GuiMarkupText(sysCostBG, Alignment().padded(8,0,0,4)); sysCost.text = "--"; @sysStats = GuiBlueprintStats(statsPopup, recti(vec2i(0, 30), vec2i(246, 200))); @hexNameBG = GuiSkinElement(statsPopup, recti(0, 0, 246, 30), SS_SubTitle); @hexName = GuiText(hexNameBG, recti(12, 0, 250, 30)); hexName.font = FT_Medium; @hexCostBG = GuiSkinElement(statsPopup, recti(0,0,246,30), SS_HorizBar); hexCostBG.color = Color(0x888888ff); hexCostBG.padding = recti(0,-4,0,-4); @hexCost = GuiMarkupText(hexCostBG, Alignment().padded(8,0,0,4)); hexCost.text = "--"; @hexStats = GuiBlueprintStats(statsPopup, recti(vec2i(0, 30), vec2i(246, 200))); } statPanel.visible = true; } else { if(statPanel !is null) statPanel.visible = false; } updateAbsolutePosition(); } void updateAbsolutePosition() { if(bp !is null) { updateStatsPos(); if(statPanel !is null && statPanel.visible) { bp.alignment = Alignment(Left, Top, Right-250, Bottom).padded(padding.topLeft.x, padding.topLeft.y, padding.botRight.x, padding.botRight.y); } else { bp.alignment = Alignment().padded(padding.topLeft.x, padding.topLeft.y, padding.botRight.x, padding.botRight.y); } } BaseGuiElement::updateAbsolutePosition(); } void updateGlobalStats() { if(globalStats !is null && dsg !is null) { globalStats.setStats(getDesignStats(dsg)); int build = 0, maintain = 0; double labor = 0.0; getBuildCost(dsg, build, maintain, labor); shipCost.text = format("[offset=4][img=ResourceIcon::0;24/] [vspace=4]$1" "[/vspace][offset=140][img=ResourceIcon::6;24/] [vspace=4]$2[/vspace][/offset][/offset]", formatMoney(build, maintain), standardize(labor, dsg.size >= 16)); } } void updateStats() { if(statsPopup is null) return; if(dsg is null) { statsPopup.visible = false; return; } vec2i hex = bp.hexHovered; if(hex.x < 0 || hex.y < 0 || hex.x >= dsg.hull.gridSize.x || hex.y >= dsg.hull.gridSize.y) { statsPopup.visible = false; return; } const Subsystem@ sys = dsg.subsystem(vec2u(hex)); if(sys is null) { statsPopup.visible = false; return; } statsPopup.visible = true; statsPopup.color = sys.type.color; sysNameBG.position = vec2i(2, 0); sysName.text = sys.type.name; sysNameBG.color = sys.type.color; sysCostBG.position = vec2i(2, 30); sysStats.position = vec2i(2, 28 + 26); sysStats.setStats(getSubsystemStats(dsg, sys)); hexNameBG.position = vec2i(2, sysStats.Position.botRight.y + 6); hexNameBG.color = sys.type.color; auto@ mod = dsg.module(vec2u(hex)); string txt; if(mod !is null && mod !is sys.type.defaultModule) { if(mod is sys.type.coreModule) txt = locale::SUBSYS_CORE+locale::SUBSYS_AT; else txt = mod.name+locale::SUBSYS_AT; } else { txt = locale::SUBSYS_HEX; } txt += hex.x+", "+hex.y; hexName.text = txt; hexCostBG.position = vec2i(2, sysStats.Position.botRight.y + 36); hexStats.position = vec2i(2, sysStats.Position.botRight.y + 34 + 26); hexStats.setStats(getHexStats(dsg, vec2u(hex))); statsPopup.size = vec2i(250, hexStats.Position.botRight.y); updateStatsPos(); double build = 0, maintain = 0, labor = 0; if(sys.has(HV_BuildCost)) build = sys.total(HV_BuildCost); if(sys.has(HV_MaintainCost)) maintain = sys.total(HV_MaintainCost); if(sys.has(HV_LaborCost)) labor = sys.total(HV_LaborCost); sysCost.text = format("[color=#ccc][offset=16][img=ResourceIcon::0;24/] [vspace=4]$1" "[/vspace][offset=130][img=ResourceIcon::6;24/] [vspace=4]$2[/vspace][/offset][/offset][/color]", formatMoney(ceil(build), ceil(maintain)), standardize(labor, dsg.size >= 16)); if(sys.has(HV_BuildCost)) build = dsg.variable(vec2u(hex), HV_BuildCost); if(sys.has(HV_MaintainCost)) maintain = dsg.variable(vec2u(hex), HV_MaintainCost); if(sys.has(HV_LaborCost)) labor = dsg.variable(vec2u(hex), HV_LaborCost); hexCost.text = format("[color=#ccc][offset=16][img=ResourceIcon::0;24;#ffffffaa/] [vspace=4]$1" "[/vspace][offset=130][img=ResourceIcon::6;24;#ffffffaa/] [vspace=4]$2[/vspace][/offset][/offset][/color]", formatMoney(ceil(build), ceil(maintain)), standardize(labor, dsg.size >= 16)); updateStatsPos(); } void updateStatsPos() { if(statPanel is null) return; vec2i center = AbsolutePosition.center; vec2i pos; if(statPanel.size.height - globalStats.size.height - 60 > statsPopup.size.height) { pos.x = statPanel.rect.topLeft.x; pos.y = statPanel.rect.topLeft.y + globalStats.size.height + 30; } else { pos.x = bp.rect.botRight.x - statsPopup.size.width; pos.y = bp.rect.topLeft.y; if(mousePos.x > bp.absolutePosition.center.x) pos.x = bp.rect.topLeft.x; } statsPopup.position = pos; } bool onGuiEvent(const GuiEvent& event) { if(event.caller is bp) { switch(event.type) { case GUI_Clicked: return true; case GUI_Hover_Changed: updateStats(); break; } } return BaseGuiElement::onGuiEvent(event); } void clear() { @dsg = null; const Hull@ hl = null; bp.display(hl); started = false; loaded = false; } void draw() { update(); if(statPanel !is null) skin.draw(SS_PlainBox, SF_Normal, statPanel.absolutePosition); BaseGuiElement::draw(); } } /* }}} */ /* Stat display list element {{{*/ class GuiBlueprintStats : BaseGuiElement { array boxes; GuiBlueprintStats(IGuiElement@ ParentElement, const recti& pos) { super(ParentElement, pos); updateAbsolutePosition(); } void updateAbsolutePosition() { uint h = 6; for(uint i = 0, cnt = boxes.length; i < cnt; ++i) { boxes[i].updateAbsolutePosition(); boxes[i].position = vec2i(0, h); h += boxes[i].size.height; } size = vec2i(size.width, h+12); BaseGuiElement::updateAbsolutePosition(); } void setStats(DesignStats@ stats) { uint oldCnt = boxes.length; uint newCnt = 0; for(uint i = 0, cnt = stats.stats.length; i < cnt; ++i) { if(stats.stats[i].secondary != -1) continue; if(newCnt >= boxes.length) boxes.insertLast(StatBox(this)); boxes[newCnt].update(stats, i); newCnt += 1; } for(uint i = newCnt; i < oldCnt; ++i) boxes[i].remove(); boxes.length = newCnt; updateAbsolutePosition(); } }; string shipStat(double value) { uint decimals = 0; if(value <= 10.0) decimals = 2; else if(value <= 100.0) decimals = 1; if(decimals == 2 && abs(value - round(value)) < 0.01) decimals = 0; if(decimals == 1 && abs(value - round(value)) < 0.1) decimals = 0; string fmt = toString(value, decimals); int index = fmt.length - decimals - 3; while(index > 0) { fmt = fmt.substr(0,index) + "," + fmt.substr(index); index -= 3; } return fmt; } class StatBox : BaseGuiElement { DesignStats@ stats; uint index; float pct = 0.f; GuiSprite@ icon; GuiText@ name; GuiText@ value; Color color; StatBox(GuiBlueprintStats@ disp) { super(disp, recti(0,0,100,40)); @icon = GuiSprite(this, Alignment(Left+2, Top+2, Left+38, Bottom-2)); @name = GuiText(this, Alignment(Left+46, Top+5, Right-8, Bottom-5)); name.font = FT_Bold; name.vertAlign = 0.0; @value = GuiText(this, Alignment(Left+46, Top+5, Right-8, Bottom-5)); value.vertAlign = 1.0; value.horizAlign = 1.0; value.stroke = colors::Black; addLazyMarkupTooltip(this, width = 400); updateAbsolutePosition(); } string get_tooltip() override { string tt = ""; int globIndex = stats.stats[index].index; for(uint i = 0, cnt = stats.stats.length; i < cnt; ++i) { auto@ stat = stats.stats[i]; if(i != index && stat.secondary != globIndex) continue; string val; if(i < stats.used.length && stats.used[i] >= 0.f) val = shipStat(stats.used[i]) + " / " + shipStat(stats.values[i]); else val = shipStat(stats.values[i]); if(stat.suffix.length != 0) { val += " "; val += stat.suffix; } if(tt.length != 0) tt += "\n\n"; tt += format("[img=$1;40][font=Subtitle][color=$4]$2[/color][offset=200]$5[/offset][/font]\n[vspace=6/]$3[/img]", getSpriteDesc(stat.icon), stat.name, stat.description, toString(stat.color), val); } return tt; } void updateAbsolutePosition() { size = vec2i(parent.size.width, size.height); BaseGuiElement::updateAbsolutePosition(); } void update(DesignStats@ stats, uint index) { @this.stats = stats; this.index = index; auto@ stat = stats.stats[index]; float val = stats.values[index]; float used = -1.f; if(stats.used.length > index) used = stats.used[index]; uint height = 40; if(stat.display == SDM_Short) height = 28; size = vec2i(size.width, height); icon.desc = stat.icon; name.text = stat.name; color = stat.color; name.color = color; value.color = colors::White; string txt; if(used >= 0.f) { txt = shipStat(used) + " / " + shipStat(val); if(used > val) { value.color = colors::Red; value.font = FT_Bold; } else { value.font = FT_Normal; } pct = clamp(used / val, 0.0, 1.0); } else { if(stat.display == SDM_Short && val >= 100000) txt = standardize(val, true); else txt = shipStat(val); value.font = FT_Normal; pct = 0.f; } if(stat.suffix.length != 0) { txt += " "; txt += stat.suffix; } value.text = txt; } void draw() { skin.draw(SS_HorizBar, SF_Normal, AbsolutePosition, color); if(pct > 0.f) { recti pos = AbsolutePosition.padded(46, 30, 4, 4); skin.draw(SS_ProgressBarBG, SF_Normal, pos, Color(0x000000ff)); pos.botRight.x = pos.topLeft.x + (double(pos.width) * pct); skin.draw(SS_ProgressBar, SF_Normal, pos.padded(1), color); } BaseGuiElement::draw(); } }; /** }}} */