1544 lines
41 KiB
ActionScript
1544 lines
41 KiB
ActionScript
#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<uint> 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<uint8> prevDamage;
|
|
array<double> damagedAt;
|
|
|
|
//Draw buffers
|
|
array<recti> emptyFloors;
|
|
array<Color> emptyColors;
|
|
|
|
array<recti> hexFloors;
|
|
array<Color> 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<DownloadedComment> 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<StatBox@> 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();
|
|
}
|
|
};
|
|
/** }}} */
|