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

3728 lines
94 KiB
ActionScript

import tabs.Tab;
import elements.GuiPanel;
import elements.GuiText;
import elements.GuiTextbox;
import elements.GuiDropdown;
import elements.GuiSkinElement;
import elements.GuiBlueprint;
import elements.GuiButton;
import elements.GuiSprite;
import elements.GuiMarkupText;
import elements.GuiAccordion;
import elements.GuiProgressbar;
import elements.MarkupTooltip;
from elements.GuiBlueprint import HEX_SIZE, SysDrawMode;
from dialogs.DesignExportDialog import exportDesign;
import dialogs.InputDialog;
import resources;
import design_stats;
import util.design_export;
import util.design_designer;
import icons;
import heralds_icons;
import designs;
import design_settings;
from traits import getTraitID;
import Tab@ createDesignOverviewTab() from "tabs.DesignOverviewTab";
import const Design@ createRandomDesign(uint type, int size, Empire@ emp) from "util.random_designs";
from tabs.tabbar import browseTab, popTab, ActiveTab;
const double TURRET_TICK_ROTATION = twopi / 36.0;
Color warningColor(0xca4b06ff);
Color errorColor(0xff0000ff);
const int HEAD_XOFF = 12;
const int STAT_XOFF = 12;
const int STAT_COLLAPSE = 0;
const int FILTER_WIDTH = 80;
int buttonHeight = 60;
bool SHOW_ARC_UNDERLAY = false;
bool SHOW_HULL_UNDERLAY = false;
bool SHOW_HULL_WEIGHTS = false;
enum ButtonMask {
B_LEFT = 1,
B_RIGHT = 2,
B_MIDDLE = 4,
}
/** Subsystem list element {{{*/
class SubsystemSelector : BaseGuiElement {
Construction@ cons;
int[] subsystems;
int[] modules;
int hovered = -1;
string category;
array<GuiButton@> applyButtons;
SubsystemSelector(Construction@ cons, const string& cat) {
@this.cons = cons;
super(cons.syslist, recti());
category = cat;
updateAbsolutePosition();
visible = false;
}
void remove() {
@cons = null;
BaseGuiElement::remove();
}
bool contains(const SubsystemDef@ def) {
if(modules.length > 0)
return false;
return subsystems.find(def.index) != -1;
}
bool contains(const ModuleDef@ def) {
if(modules.length == 0)
return false;
for(uint i = 0, cnt = subsystems.length; i < cnt; ++i) {
auto@ def = getSubsystemDef(subsystems[i]);
auto@ mod = def.modules[modules[i]];
if(mod.id == def.id)
return true;
}
return false;
}
string get_tooltip() {
if(hovered >= 0 && hovered < int(subsystems.length)) {
const SubsystemDef@ def = getSubsystemDef(subsystems[hovered]);
string name, description;
Sprite sprt;
uint drawMode = 0;
Color color;
if(uint(hovered) < modules.length) {
auto@ mod = def.modules[modules[hovered]];
sprt = mod.sprite;
name = mod.name;
description = mod.description;
color = mod.color;
drawMode = mod.drawMode;
}
else {
if(def.coreModule !is null) {
sprt = def.coreModule.sprite;
drawMode = def.coreModule.drawMode;
}
else {
sprt = def.defaultModule.sprite;
drawMode = def.defaultModule.drawMode;
}
name = def.name;
description = def.description;
color = def.color;
}
int isize = sprt.size.width;
double ifac = double(isize) / 128;
int off = 42 + (sprt.size.height - 128);
string img;
if(drawMode == 0) {
img = format("[vspace=-$2/][center][img=$1;$3/][/center]",
getSpriteDesc(sprt), toString(off), toString(isize));
}
else if(drawMode == 1) {
sprt.index += 2;
img = format("[center][img=$1;$3/][/center]",
getSpriteDesc(sprt), toString(off), toString(isize/2));
}
else if(drawMode == 3) {
img = format("[center][img=$1;55/][/center]",
getSpriteDesc(sprt));
}
return format("[font=Medium][color=$4]$2[/color][/font]$1$3",
img, name, description, toString(color));
}
return "";
}
bool onGuiEvent(const GuiEvent& event) {
switch(event.type) {
case GUI_Mouse_Entered:
if(event.caller is this)
hovered = -1;
break;
case GUI_Mouse_Left:
if(!isAncestorOf(event.other))
hovered = -1;
break;
}
if(cast<GuiButton>(event.caller) !is null) {
if(event.type == GUI_Clicked) {
emitConfirmed();
return true;
}
}
return BaseGuiElement::onGuiEvent(event);
}
int getOffsetItem(const vec2i& offset) {
if(offset.x < 0 || offset.y < 0)
return -1;
if(offset.x > AbsolutePosition.width || offset.y > AbsolutePosition.height)
return -1;
uint row = offset.y / buttonHeight;
if(row >= subsystems.length())
return -1;
return int(row);
}
void updateAbsolutePosition() {
size = vec2i(Parent.absolutePosition.width, subsystems.length * buttonHeight);
BaseGuiElement::updateAbsolutePosition();
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(isAncestorOf(source)) {
vec2i mouse = vec2i(event.x, event.y);
vec2i offset = mouse - AbsolutePosition.topLeft;
int prevHovered = hovered;
hovered = getOffsetItem(offset);
if(prevHovered != hovered && Tooltip !is null)
Tooltip.update(skin, this);
if(source is this) {
switch(event.type) {
case MET_Button_Down:
if(hovered != -1)
return true;
break;
case MET_Button_Up:
if(hovered != -1) {
emitChange();
return true;
}
break;
}
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
void draw() {
int w = AbsolutePosition.width, h = buttonHeight;
vec2i pos = AbsolutePosition.topLeft;
uint applyInd = 0;
const Font@ fnt = skin.getFont(FT_Bold);
uint fntHeight = fnt.getLineHeight();
for(int i = 0, cnt = subsystems.length(); i < cnt; ++i) {
vec2i btnPos = pos + vec2i(0, i * buttonHeight);
setClip(recti_area(btnPos, vec2i(w, h)).clipAgainst(AbsoluteClipRect));
const SubsystemDef@ def = getSubsystemDef(subsystems[i]);
string name;
Sprite picture;
uint drawMode = 0;
bool selected = false;
Color color;
if(uint(i) >= modules.length) {
name = def.name;
color = def.color;
if(def.coreModule !is null) {
picture = def.coreModule.sprite;
drawMode = def.coreModule.drawMode;
}
else {
picture = def.defaultModule.sprite;
drawMode = def.defaultModule.drawMode;
}
selected = cons.activeTool !is cons.moduleTool && cons.paintTool.def is def;
}
else {
auto@ module = def.modules[modules[i]];
name = module.name;
color = module.color;
picture = module.sprite;
drawMode = module.drawMode;
selected = cons.activeTool is cons.moduleTool && cons.moduleTool.mod.id == module.id;
}
//Figure out draw mode
uint sprite = 0;
if(i == hovered)
sprite = 1;
if(drawMode == 1)
picture.index += 2;
//Draw the background
spritesheet::SubsystemButton.draw(sprite,
recti_area(btnPos, vec2i(w, h)), color);
//Modify size to draw size
double factor = double(h*0.75) / HEX_SIZE.y;
vec2i hSize = vec2i(h*0.75, h*0.75);
vec2i modSize = picture.size;
modSize.x = double(modSize.x) * factor;
modSize.y = double(modSize.y) * factor;
if(drawMode == 1)
modSize = vec2i(double(h)*1.8, double(h)*1.8/picture.aspect);
else if(drawMode == 3)
modSize = vec2i(buttonHeight-5, buttonHeight-5);
//Offset for floating
vec2i pos = btnPos + vec2i(4,h*0.25);
if(modSize.y > hSize.y) {
if(drawMode == 1)
pos.y -= (modSize.y - hSize.y) / 2 + 10;
else
pos.y -= (modSize.y - hSize.y);
}
else
pos.y -= (modSize.y - hSize.y) / 2;
picture.draw(recti_area(pos, modSize));
//Draw glow
if(!def.isApplied) {
if(selected) {
material::SubsystemButtonGlow.draw(
recti_area(btnPos, vec2i(w, h)), color);
}
}
else {
GuiButton@ btn;
if(applyInd >= applyButtons.length) {
@btn = GuiButton(this, recti());
applyButtons.insertLast(btn);
}
else {
@btn = applyButtons[applyInd];
}
++applyInd;
btn.rect = recti_area(vec2i(w - 140, (i*buttonHeight)+24), vec2i(140, buttonHeight-24));
bool curApplied = cons.editor.data.appliedSubsystems.find(def) != -1;
if(curApplied) {
btn.text = locale::REMOVE;
btn.buttonIcon = icons::Remove;
btn.color = colors::Red;
}
else {
btn.text = locale::APPLY;
btn.buttonIcon = icons::Add;
btn.color = colors::Green;
}
if(curApplied)
skin.draw(SS_Glow, SF_Normal, recti_area(btnPos, vec2i(w, h)), color);
btn.draw();
}
//Draw the name
fnt.draw(recti_area(vec2i(4, 1) + btnPos, vec2i(w-10, 20)),
name,
color=color,
horizAlign=1.0, vertAlign=0.5,
stroke=Color(0x00000080));
clearClip();
}
for(uint i = applyInd, cnt = applyButtons.length; i < cnt; ++i)
applyButtons[i].remove();
applyButtons.length = applyInd;
}
};
/** }}} */
/** Design data representation {{{*/
final class SubsystemData {
int sysId;
vec2u core;
vec2u[] hexes;
vec3d direction;
SubsystemData() {
direction = vec3d_front();
}
const SubsystemDef@ get_def() {
return getSubsystemDef(sysId);
}
};
final class HexData {
int subsystem;
int module;
HexData() {
subsystem = -1;
module = -1;
}
HexData(int sys, int mod) {
subsystem = sys;
module = mod;
}
};
class DesignData {
const Hull@ hull;
vec2i gridSize;
double size;
array<SubsystemData@> subsystems;
array<const SubsystemDef@> appliedSubsystems;
HexData[] hexagons;
Action@ undoHead;
Action@ redoHead;
bool forceHull = false;
bool posChange = false;
DesignData() {
}
void clear() {
while(undoHead !is null) {
auto@ tmp = undoHead;
@undoHead = undoHead.previous;
@tmp.previous = null;
@tmp.next = null;
}
while(redoHead !is null) {
auto@ tmp = redoHead;
@redoHead = redoHead.previous;
@tmp.previous = null;
@tmp.next = null;
}
subsystems.length = 0;
hexagons.length = 0;
forceHull = false;
}
void resize(double shipSize) {
size = shipSize;
}
void gridChange(vec2u newSize) {
gridSize = vec2i(newSize);
hexagons.length = gridSize.x * gridSize.y;
posChange = true;
}
void load(const Design@ dsg) {
//Remove previous data
clear();
@hull = dsg.hull;
size = dsg.size;
forceHull = dsg.forceHull;
//Create grid
gridSize = dsg.hull.gridSize;
hexagons.length = gridSize.x * gridSize.y;
//Load subsystems
subsystems.resize(0);
appliedSubsystems.resize(0);
for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) {
const Subsystem@ sys = dsg.subsystem(i);
if(sys.type.isHull)
continue;
if(sys.type.isApplied) {
appliedSubsystems.insertLast(sys.type);
continue;
}
SubsystemData@ data = SubsystemData();
data.sysId = sys.type.index;
data.core = sys.core;
data.direction = sys.direction;
for(uint j = 0, jcnt = sys.hexCount; j < jcnt; ++j) {
vec2u pos = sys.hexagon(j);
data.hexes.insertLast(pos);
HexData@ hdata = hex[pos];
hdata.subsystem = i;
hdata.module = sys.module(j).index;
}
subsystems.insertLast(data);
}
}
void save(DesignDescriptor& desc) {
desc.size = size;
desc.gridSize = vec2u(gridSize);
//Add subsystems
for(uint i = 0, cnt = subsystems.length; i < cnt; ++i) {
const SubsystemDef@ def = subsystems[i].def;
desc.addSystem(def);
desc.setDirection(subsystems[i].direction);
for(uint j = 0, jcnt = subsystems[i].hexes.length(); j < jcnt; ++j) {
vec2u pos = subsystems[i].hexes[j];
HexData@ hdata = hex[pos];
desc.addHex(pos, def.modules[hdata.module]);
}
}
//Add applied subsystems
for(uint i = 0, cnt = appliedSubsystems.length; i < cnt; ++i)
desc.applySubsystem(appliedSubsystems[i]);
}
HexData@ get_hex(const vec2u& pos) {
return hexagons[pos.x + pos.y * gridSize.width];
}
SubsystemData@ get_subsystem(const vec2u& pos) {
int index = hexagons[pos.x + pos.y * gridSize.width].subsystem;
if(index == -1)
return null;
return subsystems[index];
}
void startGroup() {
act(GroupStart());
}
void continueGroup() {
//Continue the previously ended group
GroupEnd@ grp = cast<GroupEnd>(undoHead);
if(grp !is null)
@undoHead = undoHead.previous;
else
grabGroup();
}
void grabGroup() {
//Swap the group and the last action to put
//it in the undo group
Action@ act = undoHead;
startGroup();
if(act !is null) {
if(act.previous !is null)
@act.previous.next = undoHead;
@undoHead.previous = act.previous;
@undoHead.next = act;
@act.previous = undoHead;
@act.next = null;
@undoHead = act;
}
}
void endGroup() {
act(GroupEnd());
}
void act(Action@ action) {
@action.previous = undoHead;
if(undoHead !is null)
@undoHead.next = action;
@undoHead = action;
@redoHead = null;
action.act(this);
}
void undo() {
if(undoHead is null)
return;
@redoHead = undoHead;
@undoHead = undoHead.previous;
redoHead.undo(this);
}
void redo() {
if(redoHead is null)
return;
@redoHead.previous = undoHead;
@undoHead = redoHead;
@redoHead = redoHead.next;
undoHead.act(this);
}
bool hasTag(int tagId) {
for(uint i = 0, cnt = subsystems.length; i < cnt; ++i) {
if(subsystems[i].def.hasTag(tagId))
return true;
}
for(uint i = 0, cnt = appliedSubsystems.length; i < cnt; ++i) {
if(appliedSubsystems[i].hasTag(tagId))
return true;
}
return false;
}
};
void checkConnected(HexGridb& conn, DesignData& data, const vec2u& hex, int sys) {
if(conn.get(hex))
return;
conn.get(hex) = true;
for(uint i = 0; i < 6; ++i) {
HexGridAdjacency adj = HexGridAdjacency(i);
vec2u pos = hex;
if(conn.advance(pos, adj)) {
if(data.hex[pos].subsystem == sys) {
checkConnected(conn, data, pos, sys);
}
}
}
}
/** }}} */
/** Actions {{{*/
class Action {
Action@ previous;
Action@ next;
void act(DesignData@ data) {
}
void undo(DesignData@ data) {
}
};
class GroupStart : Action {
void act(DesignData@ data) {
if(next is null)
return;
Action@ act = next;
while(cast<GroupEnd@>(act) is null) {
act.act(data);
@act = act.next;
}
@data.undoHead = act;
@data.redoHead = act.next;
}
};
class GroupEnd : Action {
void undo(DesignData@ data) {
Action@ act = previous;
while(cast<GroupStart@>(act) is null) {
act.undo(data);
@act = act.previous;
}
@data.redoHead = act;
@data.undoHead = act.previous;
}
};
class PaintAction : Action {
vec2u hex;
HexData to;
PaintAction(const vec2u& Hex, const HexData& To) {
hex = Hex;
to = To;
}
void act(DesignData@ data) {
data.hex[hex] = to;
SubsystemData@ sdata = data.subsystems[to.subsystem];
sdata.hexes.insertLast(hex);
if(sdata.def.hasCore && to.module == sdata.def.coreModule.index)
sdata.core = hex;
}
void undo(DesignData@ data) {
data.subsystems[to.subsystem].hexes.remove(hex);
data.hex[hex].subsystem = -1;
}
};
class ModuleAction : Action {
vec2u hex;
int prev;
int mod;
ModuleAction(const vec2u& Hex, int module) {
hex = Hex;
mod = module;
}
void act(DesignData@ data) {
HexData@ hdata = data.hex[hex];
prev = hdata.module;
hdata.module = mod;
}
void undo(DesignData@ data) {
data.hex[hex].module= prev;
}
};
class ClearAction : Action {
vec2u hex;
HexData from;
ClearAction(const vec2u& Hex) {
hex = Hex;
}
void act(DesignData@ data) {
from = data.hex[hex];
if(from.subsystem != -1)
data.subsystems[from.subsystem].hexes.remove(hex);
data.hex[hex] = HexData(-1, -1);
}
void undo(DesignData@ data) {
if(from.subsystem != -1) {
SubsystemData@ sdata = data.subsystems[from.subsystem];
sdata.hexes.insertLast(hex);
if(sdata.def.hasCore && from.module == sdata.def.coreModule.index)
sdata.core = hex;
}
data.hex[hex] = from;
}
};
class SizeChangeAction : Action {
double fromSize;
double toSize;
SizeChangeAction(double from, double to) {
fromSize = from;
toSize = to;
}
void act(DesignData@ data) {
data.resize(toSize);
}
void undo(DesignData@ data) {
data.resize(fromSize);
}
};
class GridChangeAction : Action {
vec2u fromSize;
vec2u toSize;
GridChangeAction(vec2u from, vec2u to) {
fromSize = from;
toSize = to;
}
void act(DesignData@ data) {
data.gridChange(toSize);
}
void undo(DesignData@ data) {
data.gridChange(fromSize);
}
};
class CreateSubsystemAction : Action {
const SubsystemDef@ def;
vec2u core;
vec3d direction;
CreateSubsystemAction(const SubsystemDef@ sys, const vec2u& Core, const vec3d& direction = vec3d()) {
@def = sys;
core = Core;
this.direction = direction;
}
void act(DesignData@ data) {
SubsystemData ss;
ss.sysId = def.index;
ss.core = core;
if(!direction.zero) {
ss.direction = direction;
}
else if(def.hasTag(ST_HexLimitArc) && !data.hasTag(ST_OverrideHexArcLimit)) {
if((data.hull.active.valid(core, HEX_UpRight) && data.hull.active.get(core, HEX_UpRight))
|| (data.hull.active.valid(core, HEX_DownRight) && data.hull.active.get(core, HEX_DownRight))) {
for(uint i = 0; i < 6; ++i) {
int d = 3;
if(i % 2 == 0)
d += i/2;
else
d -= i/2+1;
auto adj = HexGridAdjacency(d);
vec2u pos = core;
if(data.hull.active.advance(pos, adj)) {
if(data.hull.active[pos])
continue;
}
double rad = hexToRadians(adj);
vec2d offset = vec2d(1.0,0.0).rotate(rad);
ss.direction.x = offset.x;
ss.direction.z = -offset.y;
break;
}
}
}
data.subsystems.insertLast(ss);
}
void undo(DesignData@ data) {
int index = data.subsystems.length - 1;
data.subsystems.removeAt(index);
for(uint i = 0, cnt = data.hexagons.length; i < cnt; ++i) {
if(data.hexagons[i].subsystem == index)
data.hexagons[i].subsystem = -1;
}
}
};
class RemoveSubsystemAction : Action {
int index;
SubsystemData prevData;
RemoveSubsystemAction(int sysIndex) {
index = sysIndex;
}
void act(DesignData@ data) {
prevData = data.subsystems[index];
data.subsystems.removeAt(index);
for(uint i = 0, cnt = data.hexagons.length; i < cnt; ++i) {
HexData@ hdata = data.hexagons[i];
if(hdata.subsystem > index)
--hdata.subsystem;
}
}
void undo(DesignData@ data) {
SubsystemData newData;
newData = prevData;
data.subsystems.insertAt(index, newData);
for(uint i = 0, cnt = data.hexagons.length; i < cnt; ++i) {
HexData@ hdata = data.hexagons[i];
if(hdata.subsystem >= index)
++hdata.subsystem;
}
}
};
class RotateAction : Action {
int sysIndex;
double rotation;
RotateAction(int sysId, double rot) {
sysIndex = sysId;
rotation = rot;
}
void rotate(DesignData@ data, double amt) {
SubsystemData@ sys = data.subsystems[sysIndex];
vec2d base(sys.direction.x, sys.direction.z);
base.rotate(double(amt));
sys.direction.x = base.x;
sys.direction.z = base.y;
}
void act(DesignData@ data) {
rotate(data, rotation);
}
void undo(DesignData@ data) {
rotate(data, -rotation);
}
};
class ApplySubsystem : Action {
const SubsystemDef@ type;
ApplySubsystem(const SubsystemDef@ type) {
@this.type = type;
}
void act(DesignData@ data) {
data.appliedSubsystems.insertLast(type);
}
void undo(DesignData@ data) {
data.appliedSubsystems.remove(type);
}
};
class RemoveAppliedSubsystem : Action {
const SubsystemDef@ type;
RemoveAppliedSubsystem(const SubsystemDef@ type) {
@this.type = type;
}
void act(DesignData@ data) {
data.appliedSubsystems.remove(type);
}
void undo(DesignData@ data) {
data.appliedSubsystems.insertLast(type);
}
};
/** }}} */
/** Tools {{{*/
class Tool {
void grab(DesignData@ data, const vec2u& hex, int button) {
}
void hover(DesignData@ data, const vec2u& hex, int pressed) {
}
void release(DesignData@ data, const vec2u& hex, int button) {
}
void release(DesignData@ data, int button) {
}
bool scroll(DesignData@ data, const vec2u& hex, int amount) {
return false;
}
void cancel(DesignData@ data, int button) {
}
void draw(DesignData@ data, DesignEditor@ editor) {
}
};
bool checkSubsystemConsistency(DesignData@ data, int index, bool createGroup, bool& destroyed) {
SubsystemData@ sdata = data.subsystems[index];
bool createdGroup = false;
//Noncontiguous subsystems skip these checks
if(sdata.def.isContiguous) {
//Clear any hexes that are not connected
HexGridb conn;
conn.resize(data.gridSize.width, data.gridSize.height);
conn.clear(false);
//Check outward from the core (if it exists)
if(data.hex[sdata.core].subsystem == index)
checkConnected(conn, data, sdata.core, index);
//Remove any hexes that are no longer connected
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
vec2u pos = sdata.hexes[i];
if(!conn.get(pos)) {
if(createGroup) {
data.grabGroup();
createdGroup = true;
createGroup = false;
}
data.act(ClearAction(pos));
--i; --cnt;
}
}
}
//Remove the subsystem if no hexes are remaining
if(sdata.hexes.length == 0) {
if(createGroup) {
data.grabGroup();
createdGroup = true;
createGroup = false;
}
data.act(RemoveSubsystemAction(index));
destroyed = true;
}
else {
destroyed = false;
}
return createdGroup;
}
class EyedropperTool : Tool {
int sysId;
int moduleId;
EyedropperTool() {
sysId = -1;
}
void release(DesignData@ data, const vec2u& hex, int button) {
SubsystemData@ sdata = data.subsystem[hex];
sysId = sdata.sysId;
moduleId = -1;
if(sysId != -1) {
auto@ def = sdata.def;
int mod = data.hex[hex].module;
if(mod != -1 && mod != def.coreModule.index && mod != def.defaultModule.index)
moduleId = mod;
}
}
};
class ReplaceTool : Tool {
const SubsystemDef@ def;
void release(DesignData@ data, const vec2u& hex, int button) {
if(button != B_LEFT)
return;
HexData@ hdata = data.hex[hex];
if(hdata is null || hdata.subsystem == -1)
return;
SubsystemData@ sdata = data.subsystems[hdata.subsystem];
auto@ prevDef = sdata.def;
if(prevDef is def || def is null || prevDef is null)
return;
array<Action@> actions;
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i)
actions.insertLast(ClearAction(sdata.hexes[i]));
actions.insertLast(RemoveSubsystemAction(hdata.subsystem));
int newSys = data.subsystems.length-1;
actions.insertLast(CreateSubsystemAction(def, sdata.core, direction=sdata.direction));
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
const ModuleDef@ mod = def.defaultModule;
if(def.coreModule !is null && prevDef.coreModule !is null && data.hex[sdata.hexes[i]].module == prevDef.coreModule.index)
@mod = def.coreModule;
if(prevDef.coreModule is null && def.coreModule !is null && i == 0)
@mod = def.coreModule;
actions.insertLast(PaintAction(sdata.hexes[i], HexData(newSys, mod.index)));
}
data.startGroup();
for(uint i = 0, cnt = actions.length; i < cnt; ++i)
data.act(actions[i]);
data.endGroup();
}
};
class PaintTool : Tool {
const SubsystemDef@ def;
int draggingSystem;
int firstButton;
bool started;
bool modified;
bool moduleRemove;
PaintTool() {
draggingSystem = -1;
firstButton = 0;
started = false;
modified = false;
moduleRemove = false;
}
void clearHex(DesignData@ data, const vec2u& hex, bool checkModule = false) {
HexData@ hdata = data.hex[hex];
int sys = hdata.subsystem;
if(sys == -1)
return;
if(modified && !started) {
started = true;
data.grabGroup();
}
bool isModule = false;
const SubsystemDef@ def = data.subsystems[sys].def;
if(checkModule || moduleRemove) {
isModule = (!def.hasCore || hdata.module != def.coreModule.index) &&
hdata.module != def.defaultModule.index;
}
if(checkModule)
moduleRemove = isModule;
if(moduleRemove) {
if(isModule) {
data.act(ModuleAction(hex, def.defaultModule.index));
modified = true;
}
}
else {
data.act(ClearAction(hex));
modified = true;
}
bool destroyed = false;
if(checkSubsystemConsistency(data, sys, !started, destroyed))
started = true;
}
void paintHex(DesignData@ data, const vec2u& hex, int mod) {
HexData@ hdata = data.hex[hex];
if(hdata.subsystem == draggingSystem)
return;
//Add the clear action for previous hex
int oldsys = hdata.subsystem;
if(oldsys != -1) {
if(!started) {
if(modified)
data.grabGroup();
else
data.startGroup();
started = true;
}
data.act(ClearAction(hex));
bool destroyed = false;
checkSubsystemConsistency(data, oldsys, false, destroyed);
if(destroyed) {
if(draggingSystem >= oldsys)
--draggingSystem;
}
}
//Add the paint action
data.act(PaintAction(hex, HexData(draggingSystem, mod)));
}
bool canPaint(DesignData@ data, const vec2u& hex, bool initial) {
if(def is null)
return false;
if(!initial) {
//Make sure we're painting a contiguous subsystem
if(def.isContiguous) {
bool found = false;
for(uint i = 0; i < 6; ++i) {
HexGridAdjacency adj = HexGridAdjacency(i);
vec2u pos = hex;
if(!advanceHexPosition(pos, vec2u(data.gridSize), adj))
continue;
HexData@ other = data.hex[pos];
if(other.subsystem == draggingSystem) {
found = true;
break;
}
}
if(!found)
return false;
}
}
return true;
}
void grab(DesignData@ data, const vec2u& hex, int button) {
if(firstButton != 0)
return;
firstButton = button;
started = false;
modified = false;
if(button == B_LEFT) {
if(def is null)
return;
//Find the system to drag from or create
draggingSystem = -1;
if(!def.isContiguous) {
for(uint i = 0, cnt = data.subsystems.length; i < cnt; ++i) {
if(data.subsystems[i].sysId == def.index) {
draggingSystem = i;
break;
}
}
}
else {
//Check if we're dragging from a start point
//in a valid subsystem
HexData@ hdata = data.hex[hex];
if(hdata.subsystem != -1 && data.subsystems[hdata.subsystem].sysId == def.index && !ctrlKey) {
draggingSystem = hdata.subsystem;
return;
}
else {
//Check if adjacent to the same subsystem to connect to
if(!ctrlKey) {
double closestDist = -1.0;
for(uint i = 0; i < 6; ++i) {
HexGridAdjacency adj = HexGridAdjacency(i);
vec2u pos = hex;
if(!advanceHexPosition(pos, vec2u(data.gridSize), adj))
continue;
HexData@ other = data.hex[pos];
if(other.subsystem != -1 && data.subsystems[other.subsystem].sysId == def.index) {
vec2u core = data.subsystems[other.subsystem].core;
double dist = getHexPosition(hex).distanceTo(getHexPosition(core));
if(draggingSystem == -1 || dist < closestDist) {
draggingSystem = other.subsystem;
closestDist = dist;
}
}
}
}
}
}
if(!canPaint(data, hex, true)) {
started = false;
modified = false;
}
else if(draggingSystem == -1) {
started = true;
modified = true;
data.startGroup();
const ModuleDef@ mod;
if(def.hasCore)
@mod = def.coreModule;
else
@mod = def.defaultModule;
draggingSystem = data.subsystems.length;
data.act(CreateSubsystemAction(def, hex));
paintHex(data, hex, mod.index);
}
else {
paintHex(data, hex, def.defaultModule.index);
modified = true;
}
}
else if(button == B_RIGHT) {
clearHex(data, hex, true);
}
}
void hover(DesignData@ data, const vec2u& hex, int pressed) {
if(firstButton == B_LEFT) {
if(def is null)
return;
HexData@ hdata = data.hex[hex];
if(hdata.subsystem == draggingSystem)
return;
if(!canPaint(data, hex, false))
return;
//Start the group if needed
if(modified && !started) {
started = true;
data.grabGroup();
}
paintHex(data, hex, def.defaultModule.index);
modified = true;
}
else if(firstButton == B_RIGHT) {
clearHex(data, hex);
}
}
void release(DesignData@ data, const vec2u& hex, int button) {
if(button != firstButton)
return;
if(button == B_LEFT)
draggingSystem = -1;
if(started)
data.endGroup();
started = false;
firstButton = 0;
}
void cancel(DesignData@ data, int button) {
if(button != firstButton)
return;
if(button == B_LEFT)
draggingSystem = -1;
if(started)
data.endGroup();
started = false;
firstButton = 0;
}
bool scroll(DesignData@ data, const vec2u& hex, int amount) {
HexData@ hdata = data.hex[hex];
int sysId = hdata.subsystem;
if(sysId == -1)
return false;
SubsystemData@ sys = data.subsystems[sysId];
const SubsystemDef@ def = sys.def;
if(!def.hasTag(ST_Rotatable))
return false;
if(hex != sys.core)
return false;
double amt = double(amount) * TURRET_TICK_ROTATION;
if(data.undoHead !is null) {
RotateAction@ act = cast<RotateAction>(data.undoHead);
if(act !is null && act.sysIndex == sysId) {
act.rotate(data, amt);
act.rotation += amt;
return true;
}
}
data.act(RotateAction(sysId, amt));
return true;
}
};
class MoveTool : Tool {
vec2u origin;
bool moving;
bool valid;
bool lastShift;
bool lastControl;
vec2u lastHex;
MoveTool() {
moving = false;
valid = false;
lastShift = false;
lastControl = false;
}
void grab(DesignData@ data, const vec2u& hex, int button) {
if(button != B_LEFT)
return;
HexData@ hdata = data.hex[hex];
if(hdata.subsystem == -1)
return;
origin = hex;
moving = true;
valid = true;
}
void release(DesignData@ data, const vec2u& hex, int button) {
if(button != B_LEFT)
return;
checkValid(data, hex);
moving = false;
if(!valid) {
sound::error.play(priority=true);
return;
}
if(hex == origin)
return;
if(shiftKey) {
//Move an entire subsystem
vec2i offset = vec2i(hex) - vec2i(origin);
SubsystemData@ sdata = data.subsystem[origin];
Action@[] queue;
HexGridb conn;
conn.resize(data.gridSize.width, data.gridSize.height);
conn.clear(false);
if(ctrlKey)
queue.insertLast(CreateSubsystemAction(sdata.def, vec2u()));
//Queue moves for all the hexes
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
vec2u pos = sdata.hexes[i];
vec2u newPos = vec2u(vec2i(pos) + offset);
if(hex.x % 2 != origin.x % 2) {
if(pos.x % 2 != origin.x % 2) {
if(pos.x % 2 == 0) {
newPos.y -= 1;
}
else {
newPos.y += 1;
}
}
}
if(!ctrlKey && !conn.get(pos))
queue.insertLast(ClearAction(pos));
if(data.hex[newPos].subsystem != -1)
queue.insertLast(ClearAction(newPos));
if(ctrlKey)
queue.insertLast(PaintAction(newPos,
HexData(data.subsystems.length,
data.hex[pos].module)));
else
queue.insertLast(PaintAction(newPos, data.hex[pos]));
conn.get(newPos) = true;
}
//Execute the queued actions
data.startGroup();
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
data.act(queue[i]);
//Check all subsystems for consistency
for(uint i = 0, cnt = data.subsystems.length; i < cnt; ++i) {
bool destroyed = false;
checkSubsystemConsistency(data, i, false, destroyed);
if(destroyed) {
--i;
--cnt;
}
}
data.endGroup();
}
else {
HexData from = data.hex[origin];
HexData to = data.hex[hex];
if(from.subsystem == -1)
return;
data.startGroup();
//Clear previous hexes
if(!ctrlKey)
data.act(ClearAction(origin));
if(to.subsystem != -1)
data.act(ClearAction(hex));
//Paint next hex
data.act(PaintAction(hex, from));
//When moving inside the same subsystem,
//we swap the tiles
if(to.subsystem == from.subsystem)
data.act(PaintAction(origin, to));
//Do consistency checks on both subsystems
bool destroyed = false;
checkSubsystemConsistency(data, from.subsystem, false, destroyed);
if(destroyed && to.subsystem > from.subsystem)
--to.subsystem;
if(to.subsystem != -1 && to.subsystem != from.subsystem)
checkSubsystemConsistency(data, to.subsystem, false, destroyed);
data.endGroup();
}
}
void checkConnected(HexGridb& conn, DesignData& data, const vec2u& hex, int sys, const vec2u&in toHex) {
if(conn.get(hex))
return;
conn.get(hex) = true;
for(uint i = 0; i < 6; ++i) {
HexGridAdjacency adj = HexGridAdjacency(i);
vec2u pos = hex;
if(conn.advance(pos, adj)) {
if(!ctrlKey && pos == origin)
continue;
if(data.hex[pos].subsystem == sys || pos == toHex) {
checkConnected(conn, data, pos, sys, toHex);
}
}
}
}
void checkValid(DesignData@ data, const vec2u& hex) {
if(!moving)
return;
HexData@ from = data.hex[origin];
HexData@ to = data.hex[hex];
SubsystemData@ sdata = data.subsystems[from.subsystem];
lastShift = shiftKey;
lastControl = ctrlKey;
lastHex = hex;
if(shiftKey) {
//Moving an entire subsystem is always valid unless
//moved out of bounds
vec2i offset = vec2i(hex) - vec2i(origin);
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
vec2u pos = sdata.hexes[i];
vec2i newPos = vec2i(pos) + offset;
if(hex.x % 2 != origin.x % 2) {
if(pos.x % 2 != origin.x % 2) {
if(pos.x % 2 == 0) {
newPos.y -= 1;
}
else {
newPos.y += 1;
}
}
}
if(newPos.x < 0 || newPos.x >= data.gridSize.width ||
newPos.y < 0 || newPos.y >= data.gridSize.height) {
valid = false;
return;
}
}
valid = true;
}
else {
//Cannot duplicate unique modules
if(ctrlKey) {
const ModuleDef@ mod = sdata.def.modules[from.module];
if(mod.unique) {
valid = false;
return;
}
}
//Can always move for noncontiguous systems
if(!sdata.def.isContiguous) {
valid = true;
return;
}
//Can always move within a subsystem
if(to.subsystem == from.subsystem) {
valid = true;
return;
}
//Check if anything would be deleted
HexGridb conn;
conn.resize(data.gridSize.width, data.gridSize.height);
conn.clear(false);
if(origin == sdata.core)
checkConnected(conn, data, hex, from.subsystem, hex);
else
checkConnected(conn, data, sdata.core, from.subsystem, hex);
//If the target hex is disconnected, the move is invalid
if(!conn.get(hex)) {
valid = false;
return;
}
//If any hex would be cleared by this move, it is invalid
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
vec2u pos = sdata.hexes[i];
if(pos != origin && !conn.get(pos)) {
valid = false;
return;
}
}
valid = true;
}
}
void hover(DesignData@ data, const vec2u& hex, int pressed) {
checkValid(data, hex);
}
void cancel(DesignData@ data, int button) {
if(button == B_LEFT)
moving = false;
}
void drawHex(DesignData@ data, DesignEditor@ editor, const vec2u& hex, const vec2u&in forOrigin) {
HexData@ hdata = data.hex[hex];
if(hdata.subsystem == -1)
return;
SubsystemData@ sdata = data.subsystems[hdata.subsystem];
recti pos = recti_area(vec2i(
mousePos.x - (editor.construction.display.hexSize.width / 2),
mousePos.y - (editor.construction.display.hexSize.height / 2)),
vec2i(editor.construction.display.hexSize.width,
editor.construction.display.hexSize.height));
if(hex != forOrigin) {
vec2d relOff = getHexPosition(hex) - getHexPosition(forOrigin);
relOff.x *= editor.construction.display.hexSize.width;
relOff.y *= editor.construction.display.hexSize.height;
pos += vec2i(relOff);
}
const SubsystemDef@ def = sdata.def;
const ModuleDef@ mod = def.modules[hdata.module];
//Module picture
vec2i modSize = mod.sprite.size;
vec2i hexSize = editor.construction.display.hexSize;
double factor = editor.construction.display.hexFactor;
modSize.x = double(modSize.x) * factor;
modSize.y = double(modSize.y) * factor;
vec2i modPos = pos.topLeft;
modPos.x -= (modSize.x - hexSize.x) / 2;
if(modSize.y > hexSize.y)
modPos.y -= (modSize.y - hexSize.y);
else
modPos.y -= (modSize.y - hexSize.y) / 2;
double rotation = 0.0;
if(mod is def.coreModule && def.hasTag(ST_Rotatable)) {
vec3d rot = sdata.direction;
vec2d rightvec(1.0, 0.0);
vec2d rotvec(rot.x, rot.z);
rotation = rightvec.getRotation(rotvec);
}
Color color;
if(valid)
color = Color(0xffffffd0);
else
color = Color(0xff808080);
switch(mod.drawMode) {
case SDM_Static:
if(rotation != 0)
mod.sprite.draw(recti_area(modPos, modSize), color, rotation);
else
mod.sprite.draw(recti_area(modPos, modSize), color);
break;
case SDM_Rotatable: {
vec3d rot = sdata.direction;
vec2d rightvec(1.0, 0.0);
vec2d rotvec(rot.x, -rot.z);
rotation = rightvec.getRotation(rotvec);
if(modSize.y > hexSize.y)
modPos.y += (modSize.y - hexSize.y) / 2;
Sprite sprt = mod.sprite;
sprt.index += HexGridAdjacency(radiansToHex(rotation));
double diffrot = hexToRadians(HexGridAdjacency(sprt.index)) - rotation;
sprt.draw(recti_area(modPos, modSize), color, diffrot);
} break;
}
}
void draw(DesignData@ data, DesignEditor@ editor) {
if(!moving)
return;
if(lastShift != shiftKey || lastControl != ctrlKey)
checkValid(data, lastHex);
drawHex(data, editor, origin, origin);
if(shiftKey) {
SubsystemData@ sdata = data.subsystem[origin];
for(uint i = 0, cnt = sdata.hexes.length; i < cnt; ++i) {
vec2u hex = sdata.hexes[i];
if(hex != origin)
drawHex(data, editor, hex, origin);
}
}
}
};
class ModuleTool : Tool {
const ModuleDef@ mod;
bool valid;
bool multiple;
ModuleTool() {
valid = false;
}
void checkValid(DesignData@ data, const vec2u& hex) {
HexData@ hdata = data.hex[hex];
if(hdata.subsystem == -1) {
valid = false;
return;
}
//Can only place modules within the right
//subsystem
SubsystemData@ sdata = data.subsystems[hdata.subsystem];
if(sdata.def is null || sdata.def.module(mod.id) is null) {
valid = false;
return;
}
//Cannot overwrite the core
if(sdata.def.hasCore && hdata.module == sdata.def.coreModule.index) {
valid = false;
return;
}
valid = true;
}
void clearHex(DesignData@ data, const vec2u& hex) {
HexData@ hdata = data.hex[hex];
int sys = hdata.subsystem;
if(sys == -1)
return;
const SubsystemDef@ def = data.subsystems[sys].def;
bool isModule = (!def.hasCore || hdata.module != def.coreModule.index) &&
hdata.module != def.defaultModule.index;
if(isModule)
data.act(ModuleAction(hex, def.defaultModule.index));
}
void grab(DesignData@ data, const vec2u& hex, int pressed) override {
checkValid(data, hex);
if(pressed == B_RIGHT)
clearHex(data, hex);
}
void hover(DesignData@ data, const vec2u& hex, int pressed) override {
checkValid(data, hex);
if(pressed == B_RIGHT)
clearHex(data, hex);
}
void release(DesignData@ data, const vec2u& hex, int pressed) override {
if(pressed != B_LEFT)
return;
checkValid(data, hex);
if(!valid) {
sound::error.play(priority=true);
return;
}
HexData@ hdata = data.hex[hex];
SubsystemData@ sdata = data.subsystems[hdata.subsystem];
data.act(ModuleAction(hex, sdata.def.module(mod.id).index));
}
void draw(DesignData@ data, DesignEditor@ editor) override {
recti pos = recti_area(vec2i(
mousePos.x - (editor.construction.display.hexSize.width / 2),
mousePos.y - (editor.construction.display.hexSize.height / 2)),
vec2i(editor.construction.display.hexSize.width,
editor.construction.display.hexSize.height));
if(valid) {
mod.sprite.draw(pos.aspectAligned(mod.sprite.aspect));
}
else {
drawRectangle(pos, Color(0xff000040));
mod.sprite.draw(pos.aspectAligned(mod.sprite.aspect));
}
}
};
/** }}} */
/** Construction page {{{*/
class FilterButton : GuiButton {
Construction@ cons;
string tag;
FilterButton(Construction@ parent, const string& text, const string&in Tag,
int w = FILTER_WIDTH, bool Pressed = false) {
@cons = parent;
tag = Tag;
int x = 0;
if(!parent.filterButtons.isEmpty())
x = parent.filterButtons.last.absolutePosition.botRight.x;
super(parent, recti_area(
vec2i(4 + x, 3),
vec2i(w, 22)),
text);
toggleButton = true;
pressed = Pressed;
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this && !disabled) {
switch(event.type) {
case MET_Button_Down:
if(!Hovered)
return true;
if(event.button == 0) {
for(uint i = 0, cnt = cons.filterButtons.length; i < cnt; ++i)
if(cons.filterButtons[i] !is this)
cons.filterButtons[i].pressed = false;
}
if(tag.length != 0)
Pressed = !Pressed;
else
Pressed = true;
cons.updateSubsystems();
return true;
}
}
return GuiButton::onMouseEvent(event, source);
}
};
class RandomConfig : InputDialogCallback {
int weaponCount;
double engineSize;
double hyperSize;
bool supplies;
bool support;
RandomConfig() {
reset();
}
void reset() {
weaponCount = -1;
engineSize = 1.0;
hyperSize = 1.0;
supplies = true;
support = true;
}
void apply(Designer& d) {
d.weaponCount = weaponCount;
d.supplies = supplies;
d.support = support;
d.engineSize = (engineSize - 1.0) * (d.grid.width * d.grid.height) / 30;
d.hyperSize = (hyperSize - 1.0) * (d.grid.width * d.grid.height) / 30;
}
void prompt(IGuiElement@ parent, const Design@ dsg) {
InputDialog@ dialog = InputDialog(this, parent);
dialog.addTitle(locale::DESIGN_CONFIGURE);
dialog.addSpinboxInput(locale::DESIGN_RND_WEAPONCOUNT, weaponCount, 1, -1, 10, 0);
dialog.addSpinboxInput(locale::DESIGN_RND_ENGINESIZE, engineSize, 0.1, 1, 10, 1);
if(dsg.hull.hasTag("Flagship")) {
if(playerEmpire.isUnlocked(subsystem::Hyperdrive) || playerEmpire.isUnlocked(subsystem::Jumpdrive))
dialog.addSpinboxInput(locale::DESIGN_RND_HYPERSIZE, hyperSize, 0.1, 1, 10, 1);
dialog.addToggle(locale::DESIGN_RND_SUPPLIES, supplies);
dialog.addToggle(locale::DESIGN_RND_SUPPORT, support);
}
addDialog(dialog);
dialog.focusInput();
}
void inputCallback(InputDialog@ dialog, bool accepted) {
if(accepted) {
uint i = 0;
weaponCount = dialog.getSpinboxInput(i++);
engineSize = dialog.getSpinboxInput(i++);
if(dialog.inputCount >= 4) {
if(playerEmpire.isUnlocked(subsystem::Hyperdrive) || playerEmpire.isUnlocked(subsystem::Jumpdrive))
hyperSize = dialog.getSpinboxInput(i++);
else
hyperSize = 1.0;
supplies = dialog.getToggle(i++);
support = dialog.getToggle(i++);
}
}
}
};
const string shipsetALL = "ALL";
class HullElement : GuiListText {
const Hull@ hull;
HullElement(const Hull@ hull) {
@this.hull = hull;
string hname = hull.name;
if(playerEmpire.shipset.ident == shipsetALL) {
for(uint i = 0, cnt = getShipsetCount(); i < cnt; ++i) {
auto@ sset = getShipset(i);
if(!sset.available)
continue;
if(sset.hasHull(hull)) {
hname += " ("+sset.name+")";
break;
}
}
}
super(format(locale::FORCE_HULL, hname));
}
};
class Construction : BaseGuiElement {
DesignEditor@ editor;
vec2i shownHexStats;
RandomConfig randomConfig;
GuiPanel@ displayPanel;
GuiBlueprint@ display;
GuiAccordion@ syslist;
GuiProgressbar@ sizeBar;
GuiButton@ hullButton;
GuiButton@ arcButton;
GuiButton@ centerButton;
GuiButton@ randomButton;
GuiButton@ configButton;
GuiButton@ undoButton;
GuiButton@ redoButton;
GuiButton@ paintButton;
GuiButton@ moveButton;
GuiButton@ dropButton;
GuiButton@ replaceButton;
GuiButton@ clearButton;
PaintTool paintTool;
MoveTool moveTool;
EyedropperTool dropTool;
ModuleTool moduleTool;
ReplaceTool replaceTool;
Tool@ activeTool;
Tool@ prevTool;
GuiDropdown@ hullList;
GuiSkinElement@ statsPopup;
GuiPanel@ statsPanel;
GuiBlueprintStats@ globalStats;
GuiSkinElement@ sysNameBG;
GuiText@ sysName;
GuiSkinElement@ sysCostBG;
GuiMarkupText@ sysCost;
GuiBlueprintStats@ sysStats;
GuiSkinElement@ hexNameBG;
GuiText@ hexName;
GuiBlueprintStats@ hexStats;
GuiSkinElement@ hexCostBG;
GuiMarkupText@ hexCost;
GuiMarkupText@ tip;
GuiMarkupText@ cost;
GuiMarkupText@ buildTime;
FilterButton@[] filterButtons;
int prevSelected;
int pressed;
Construction(DesignEditor@ ed, Alignment@ align) {
@editor = ed;
super(editor, align);
@activeTool = paintTool;
pressed = 0;
prevSelected = 0;
@hullList = GuiDropdown(this, Alignment(Left+270, Top+1, Right-686-36-36-60-20, Height=32));
hullList.visible = false;
updateHullList();
@arcButton = GuiButton(this, Alignment(Right-686-36-36-60, Top+2, Width=32, Height=32), spritesheet::ActionBarIcons+20);
arcButton.style = SS_IconToggle;
arcButton.color = Color(0x80a0ffff);
arcButton.toggleButton = true;
arcButton.pressed = SHOW_ARC_UNDERLAY;
setMarkupTooltip(arcButton, locale::DESIGN_SHOW_ARCS);
@hullButton = GuiButton(this, Alignment(Right-686-36-60, Top+2, Width=32, Height=32),
Sprite(spritesheet::ShipIcons, 1, Color(0x8080ffff)));
hullButton.style = SS_IconToggle;
hullButton.color = Color(0x80a0ffff);
hullButton.toggleButton = true;
hullButton.pressed = SHOW_HULL_UNDERLAY;
setMarkupTooltip(hullButton, locale::DESIGN_SHOW_HULL);
@centerButton = GuiButton(this, Alignment(Right-686-60, Top+2, Width=32, Height=32), Sprite(material::TabPlanets));
centerButton.style = SS_IconButton;
centerButton.color = Color(0x80a0ffff);
setMarkupTooltip(centerButton, locale::DESIGN_CENTER);
@randomButton = GuiButton(this, Alignment(Right-650-26, Top+2, Width=32, Height=32), Sprite(spritesheet::MenuIcons, 4, Color(0xff8040ff)));
randomButton.style = SS_IconButton;
randomButton.color = Color(0x80a0ffff);
randomButton.visible = !playerEmpire.hasTrait(getTraitID("Verdant"));
setMarkupTooltip(randomButton, locale::DESIGN_RANDOMIZE);
@configButton = GuiButton(this, Alignment(Right-650, Top+2, Width=32, Height=32), Sprite(spritesheet::MenuIcons, 7, Color(0xff8040ff)));
configButton.style = SS_IconButton;
configButton.color = Color(0xa080ffff);
configButton.visible = false;
setMarkupTooltip(configButton, locale::DESIGN_RANDOMIZE_CONFIGURE);
@undoButton = GuiButton(this, Alignment(Right-568, Top+2, Width=32, Height=32), icons::Undo);
undoButton.style = SS_IconButton;
setMarkupTooltip(undoButton, format(locale::DESIGN_UNDO, getKey(KB_DESIGN_UNDO)));
@redoButton = GuiButton(this, Alignment(Right-532, Top+2, Width=32, Height=32), icons::Redo);
redoButton.style = SS_IconButton;
setMarkupTooltip(redoButton, format(locale::DESIGN_REDO, getKey(KB_DESIGN_REDO)));
@paintButton = GuiButton(this, Alignment(Right-476, Top+2, Width=32, Height=32), icons::Paint);
paintButton.style = SS_IconToggle;
paintButton.toggleButton = true;
paintButton.pressed = true;
paintButton.color = colors::Green;
setMarkupTooltip(paintButton, format(locale::DESIGN_PAINT_TOOL, getKey(KB_DESIGN_TOOL_PAINT)));
@moveButton = GuiButton(this, Alignment(Right-440, Top+2, Width=32, Height=32), icons::Move);
moveButton.style = SS_IconToggle;
moveButton.toggleButton = true;
moveButton.color = colors::Green;
setMarkupTooltip(moveButton, format(locale::DESIGN_MOVE_TOOL, getKey(KB_DESIGN_TOOL_MOVE)));
@dropButton = GuiButton(this, Alignment(Right-404, Top+2, Width=32, Height=32), icons::Eyedrop);
dropButton.style = SS_IconToggle;
dropButton.toggleButton = true;
dropButton.color = colors::Green;
setMarkupTooltip(dropButton, format(locale::DESIGN_DROP_TOOL, getKey(KB_DESIGN_TOOL_DROP)));
@replaceButton = GuiButton(this, Alignment(Right-368, Top+2, Width=32, Height=32), icons::Replace);
replaceButton.style = SS_IconToggle;
replaceButton.toggleButton = true;
replaceButton.pressed = false;
replaceButton.color = colors::Green;
setMarkupTooltip(replaceButton, format(locale::DESIGN_REPLACE_TOOL, getKey(KB_DESIGN_TOOL_REPLACE)));
@clearButton = GuiButton(this, Alignment(Right-300, Top+2, Width=32, Height=32), icons::Clear);
clearButton.style = SS_IconButton;
clearButton.color = colors::Red;
setMarkupTooltip(clearButton, locale::DESIGN_CLEAR);
@displayPanel = GuiPanel(this, Alignment(Left+258+12, Top+32+12, Right-258-12, Bottom-44));
displayPanel.setScrollPane(true, true);
displayPanel.allowScrollDrag = false;
displayPanel.LeftDrag = false;
@display = GuiBlueprint(displayPanel, recti());
display.hoverArcs = true;
display.displayInactive = true;
display.activateAll = true;
@sizeBar = GuiProgressbar(this, Alignment(Left+258+12, Bottom-38, Right-258-12, Bottom-4));
sizeBar.strokeColor = colors::Black;
sizeBar.font = FT_Bold;
sizeBar.backColor = Color(0xffffff80);
setMarkupTooltip(sizeBar, locale::TT_DESIGN_HEX_LIMIT);
@tip = GuiMarkupText(this, Alignment(Left+258, Bottom-78, Right-258, Bottom));
tip.defaultColor = Color(0xaaaaaaff);
tip.defaultFont = FT_Italic;
tip.text = format("[align=0.5][img=MenuIcons::3;20/] $1[/align]", locale::TIP_ROTATE);
tip.visible = false;
//Stat panel
@statsPanel = GuiPanel(this, Alignment(Right-256, Top, Right-6, Bottom-4));
@globalStats = GuiBlueprintStats(statsPanel,
recti(vec2i(0, 4), vec2i(250, 230)));
@statsPopup = GuiSkinElement(this, recti_area(0, 0, 250, 200), SS_PlainBox);
statsPopup.visible = false;
@sysNameBG = GuiSkinElement(statsPopup,
recti(0, 0, 246, 30), SS_SubTitle);
@sysName = GuiText(sysNameBG,
recti(HEAD_XOFF, 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(HEAD_XOFF, 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)));
//Create subsystem list
@syslist = GuiAccordion(this, Alignment(Left+4, Top, Left+254, Bottom-4));
updateSubsystems();
@paintTool.def = getSubsystemDef(0);
updateAbsolutePosition();
}
string getKey(uint bind) {
int key = keybinds::DesignEditor.getCurrentKey(Keybind(bind), 0);
return getKeyDisplayName(key);
}
void updateDisplayPanel() {
if(display.hull is null)
return;
vec2i minim;
if(screenSize.x >= 1900)
minim = vec2i(30, 22);
else
minim = vec2i(24, 16);
vec2i psize;
psize.width = max(display.hull.active.width * minim.width, displayPanel.size.width - 20);
psize.height = max(display.hull.active.height * minim.height, displayPanel.size.height - 20);
displayPanel.MiddleDrag = psize.width > displayPanel.size.width || psize.height > displayPanel.size.height;
display.rect = recti(vec2i(), psize);
displayPanel.updateAbsolutePosition();
}
void updateAbsolutePosition() {
updateStatsPos();
BaseGuiElement::updateAbsolutePosition();
updateDisplayPanel();
}
bool get_ready() {
return editor.design.subsystemCount != 0;
}
void updateHullList() {
const Hull@ sel = null;
string type = "Flagship";
if(editor.data.hull !is null) {
if(editor.data.forceHull)
@sel = editor.data.hull.baseHull;
type = getHullTypeTag(editor.data.hull);
}
else {
type = editor.concept.getSelectedTypeTag();
}
hullList.clearItems();
hullList.addItem(GuiListText(locale::AUTO_SELECT_HULL));
hullList.selected = 0;
auto@ shipset = playerEmpire.shipset;
if(shipset !is null) {
for(uint i = 0, cnt = shipset.hullCount; i < cnt; ++i) {
auto@ hull = shipset.hulls[i];
if(getHullTypeTag(hull) != type)
continue;
hullList.addItem(HullElement(hull));
if(hull is sel)
hullList.selected = hullList.itemCount-1;
}
}
}
void update() {
undoButton.disabled = editor.data.undoHead is null;
if(undoButton.disabled)
undoButton.setIcon(icons::UndoDisabled);
else
undoButton.setIcon(icons::Undo);
redoButton.disabled = editor.data.redoHead is null;
if(redoButton.disabled)
redoButton.setIcon(icons::RedoDisabled);
else
redoButton.setIcon(icons::Redo);
//randomButton.visible = editor.concept.getSelectedTypeTag() != "Station";
//configButton.visible = randomButton.visible;
globalStats.setStats(getDesignStats(editor.design));
updateHexStats(shownHexStats, force=true);
double usedHexes = editor.design.usedHexCount;
usedHexes -= editor.design.variable(ShV_ExternalHexes);
double hexLimit = floor(max(editor.design.total(SV_HexLimit), 1.0));
double fillPct = clamp(usedHexes / hexLimit, 0.0, 1.0);
if(usedHexes > hexLimit)
sizeBar.frontColor = colors::Red;
else
sizeBar.frontColor = Color(0xff8080ff).interpolate(Color(0x80ff80ff), fillPct);
sizeBar.text = format(locale::DESIGN_HEX_LIMIT, toString(usedHexes, 0), toString(hexLimit, 0));
sizeBar.progress = fillPct;
updateDisplayPanel();
}
void updateHexStats(const vec2i& hex, bool force = false) {
if(hex == shownHexStats && !force)
return;
if(hex.x < 0 || hex.y < 0
|| hex.x >= display.hull.gridSize.x
|| hex.y >= display.hull.gridSize.y) {
shownHexStats = vec2i(-1,-1);
statsPopup.visible = false;
globalStats.visible = true;
tip.visible = false;
return;
}
shownHexStats = hex;
const Subsystem@ sys = editor.design.subsystem(vec2u(hex));
if(sys is null) {
statsPopup.visible = false;
globalStats.visible = true;
tip.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(editor.design, sys));
hexNameBG.position = vec2i(2, sysStats.Position.botRight.y + 6);
hexNameBG.color = sys.type.color;
auto@ mod = editor.design.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(editor.design, 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, editor.design.size >= 16));
if(sys.has(HV_BuildCost))
build = editor.design.variable(vec2u(hex), HV_BuildCost);
if(sys.has(HV_MaintainCost))
maintain = editor.design.variable(vec2u(hex), HV_MaintainCost);
if(sys.has(HV_LaborCost))
labor = editor.design.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, editor.design.size >= 16));
//Update tip
if(mod is sys.type.coreModule && sys.type.hasTag(ST_Rotatable))
tip.visible = true;
else
tip.visible = false;
}
array<SubsystemSelector@> selectors;
void updateSubsystems() {
updateHullList();
selectors.length = 0;
selectors.insertLast(SubsystemSelector(this, "Hulls"));
selectors.insertLast(SubsystemSelector(this, "Control"));
selectors.insertLast(SubsystemSelector(this, "Weapons"));
selectors.insertLast(SubsystemSelector(this, "Propulsion"));
selectors.insertLast(SubsystemSelector(this, "Defense"));
selectors.insertLast(SubsystemSelector(this, "FTL"));
selectors.insertLast(SubsystemSelector(this, "Misc"));
SubsystemSelector modifiers(this, "Modifiers");
dictionary haveModifiers;
selectors.insertLast(modifiers);
//Update list of subsystems
int subsysCount = getSubsystemDefCount();
for(int i = 0; i < subsysCount; ++i) {
const SubsystemDef@ def = getSubsystemDef(i);
if(!playerEmpire.isUnlocked(def))
continue;
if(editor.design !is null && !def.canUseOn(editor.design.hull))
continue;
if(def.isHull)
continue;
//Find selector
string cat;
if(def.hasTag(ST_Category))
cat = def.getTagValue(ST_Category);
if(cat.length == 0)
cat = "Misc";
SubsystemSelector@ sel;
for(uint n = 0, ncnt = selectors.length; n < ncnt; ++n) {
if(selectors[n].category == cat) {
@sel = selectors[n];
break;
}
}
if(sel is null) {
@sel = SubsystemSelector(this, cat);
selectors.insertAt(selectors.length-1, sel);
}
//Add to selector
sel.subsystems.insertLast(i);
//Add modules
for(uint n = 0, ncnt = def.moduleCount; n < ncnt; ++n) {
auto@ mod = def.modules[n];
if(mod is def.coreModule || mod is def.defaultModule)
continue;
if(!playerEmpire.isUnlocked(def, mod))
continue;
bool have = false;
if(haveModifiers.get(mod.id, have)) {
if(have)
continue;
}
have = true;
haveModifiers.set(mod.id, have);
modifiers.subsystems.insertLast(i);
modifiers.modules.insertLast(n);
}
}
MarkupTooltip tt(350, 0.f, true, false);
tt.StaticPosition = true;
tt.offset = vec2i(256, 158);
tt.LazyUpdate = false;
tt.Padding = 10;
syslist.clearSections();
for(uint i = 0, cnt = selectors.length; i < cnt; ++i) {
if(selectors[i].subsystems.length == 0) {
selectors[i].remove();
continue;
}
@selectors[i].tooltipObject = tt;
GuiPanel panel(syslist, recti());
@selectors[i].parent = panel;
selectors[i].size = vec2i(syslist.size.width, 100);
selectors[i].visible = true;
GuiButton header(syslist, recti(0,0,100,40));
header.style = SS_AccordionHeader;
GuiMarkupText text(header, Alignment().padded(10, 4));
text.defaultFont = FT_Subtitle;
string txt = localize("#SC_"+selectors[i].category);
if(txt[0] == '#')
txt = selectors[i].category;
text.text = txt;
syslist.addSection_r(header, panel);
}
if(syslist.sectionCount > 0)
syslist.openSection(0);
syslist.updateAbsolutePosition();
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is display) {
vec2u hex(display.hexHovered.x, display.hexHovered.y);
switch(event.type) {
case MET_Button_Down: {
//Ignore presses outside of bounds
if(display.hexHovered.x < 0 || display.hexHovered.y < 0)
return BaseGuiElement::onMouseEvent(event, source);
if(event.button == 0) {
if(altKey) {
@prevTool = paintTool;
@activeTool = dropTool;
}
else if(activeTool is paintTool) {
HexData@ hdata = editor.data.hex[vec2u(display.hexHovered)];
if(hdata.subsystem != -1) {
SubsystemData@ sdata = editor.data.subsystems[hdata.subsystem];
if(hdata.module != sdata.def.defaultModule.index) {
switchTool(moveTool);
@prevTool = paintTool;
}
}
}
}
else if(event.button == 2) {
SubsystemData@ sdata = editor.data.subsystem[hex];
if(sdata !is null) {
int moduleId = -1;
int sysId = sdata.sysId;
if(sysId != -1) {
auto@ def = sdata.def;
int mod = editor.data.hex[hex].module;
if(mod != -1 && (def.coreModule is null || mod != def.coreModule.index) && mod != def.defaultModule.index)
moduleId = mod;
}
switchBrush(sysId, moduleId);
}
}
int mask = 1 << event.button;
activeTool.grab(editor.data, vec2u(display.hexHovered), mask);
pressed |= mask;
editor.updateDesign();
} return true;
case MET_Button_Up: {
if(displayPanel.horiz.Dragging || displayPanel.vert.Dragging)
return BaseGuiElement::onMouseEvent(event, source);
int mask = 1 << event.button;
bool skipprev = false;
if(display.hexHovered.x < 0 || display.hexHovered.y < 0) {
activeTool.cancel(editor.data, mask);
}
else {
activeTool.release(editor.data, vec2u(display.hexHovered), mask);
if(activeTool is dropTool)
switchBrush(dropTool.sysId, dropTool.moduleId);
}
if(!skipprev && prevTool !is null) {
switchTool(prevTool);
@prevTool = null;
}
editor.updateDesign();
pressed &= ~mask;
} return true;
case MET_Scrolled: {
int amount = event.y;
//Ignore presses outside of bounds
if(display.hexHovered.x < 0 || display.hexHovered.y < 0)
return BaseGuiElement::onMouseEvent(event, source);
if(activeTool.scroll(editor.data, vec2u(display.hexHovered), amount))
editor.updateDesign();
} return true;
case MET_Moved: {
if(statsPopup.visible)
updateStatsPos();
};
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
void updateStatsPos() {
vec2i center = display.absolutePosition.center;
vec2i pos;
if(statsPanel.size.height - globalStats.size.height - 30 > statsPopup.size.height) {
pos.x = statsPanel.rect.topLeft.x;
pos.y = statsPanel.rect.topLeft.y + globalStats.size.height + 30;
globalStats.visible = true;
}
else {
pos.x = statsPanel.rect.topLeft.x;
pos.y = statsPanel.rect.topLeft.y;
globalStats.visible = false;
}
statsPopup.position = pos;
}
void clearBrush() {
@paintTool.def = null;
@replaceTool.def = null;
}
void switchBrush(int sysId, int modId = -1) {
if(sysId == -1) {
@paintTool.def = null;
@replaceTool.def = null;
@moduleTool.mod = null;
if(prevTool is moduleTool)
@prevTool = paintTool;
return;
}
const SubsystemDef@ def = getSubsystemDef(sysId);
if(modId != -1) {
auto@ module = def.modules[modId];
if(moduleTool.mod !is module) {
@moduleTool.mod = module;
uint index = 0;
for(uint i = 0, cnt = selectors.length; i < cnt; ++i) {
if(selectors[i].contains(module)) {
syslist.openSection(index, snap=false);
break;
}
if(selectors[i].visible)
++index;
}
}
@paintTool.def = null;
@replaceTool.def = null;
if(prevTool !is moduleTool)
@prevTool = moduleTool;
}
else {
if(paintTool.def !is def) {
@paintTool.def = def;
@replaceTool.def = def;
uint index = 0;
for(uint i = 0, cnt = selectors.length; i < cnt; ++i) {
if(selectors[i].contains(def)) {
syslist.openSection(index, snap=false);
break;
}
if(selectors[i].visible)
++index;
}
}
if(prevTool !is paintTool && prevTool !is replaceTool)
@prevTool = paintTool;
}
}
void switchTool(Tool@ tool) {
paintButton.pressed = tool is paintTool;
moveButton.pressed = tool is moveTool;
dropButton.pressed = tool is dropTool;
replaceButton.pressed = tool is replaceTool;
@activeTool = tool;
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is display) {
switch(event.type) {
case GUI_Clicked:
return true;
case GUI_Hover_Changed:
//Do hover stats
updateHexStats(display.hexHovered);
//Do painting
if(pressed != 0) {
if(display.hexHovered.x < 0 || display.hexHovered.y < 0)
return true;
activeTool.hover(editor.data, vec2u(display.hexHovered), pressed);
editor.updateDesign();
}
else {
if(display.hexHovered.x < 0 || display.hexHovered.y < 0)
return true;
activeTool.hover(editor.data, vec2u(display.hexHovered), 0);
}
break;
}
}
else if(cast<SubsystemSelector>(event.caller) !is null) {
auto@ sel = cast<SubsystemSelector>(event.caller);
switch(event.type) {
case GUI_Changed:
if(sel.hovered != -1) {
auto@ def = getSubsystemDef(sel.subsystems[sel.hovered]);
if(!def.isApplied) {
if(uint(sel.hovered) >= sel.modules.length) {
if(activeTool !is replaceTool)
switchTool(paintTool);
@paintTool.def = def;
@replaceTool.def = def;
}
else {
auto@ mod = def.modules[sel.modules[sel.hovered]];
@moduleTool.mod = mod;
switchTool(moduleTool);
}
}
}
break;
case GUI_Confirmed:
if(sel.hovered != -1) {
auto@ def = getSubsystemDef(sel.subsystems[sel.hovered]);
if(def.isApplied) {
if(editor.data.appliedSubsystems.find(def) == -1) {
bool grouped = false;
for(uint i = 0, cnt = editor.data.appliedSubsystems.length; i < cnt; ++i) {
auto@ other = editor.data.appliedSubsystems[i];
for(uint n = 0, ncnt = def.getTagValueCount(ST_Applied); n < ncnt; ++n) {
if(other.hasTagValue(ST_Applied, def.getTagValue(ST_Applied, n))) {
grouped = true;
editor.data.startGroup();
editor.data.act(RemoveAppliedSubsystem(other));
}
}
}
editor.data.act(ApplySubsystem(def));
if(grouped)
editor.data.endGroup();
}
else {
editor.data.act(RemoveAppliedSubsystem(def));
}
editor.updateDesign();
editor.updateAbsolutePosition();
}
}
break;
}
}
else if(event.type == GUI_Clicked) {
if(event.caller is arcButton) {
SHOW_ARC_UNDERLAY = arcButton.pressed;
return true;
}
else if(event.caller is hullButton) {
SHOW_HULL_UNDERLAY = hullButton.pressed;
return true;
}
else if(event.caller is centerButton) {
editor.construction.centerDesign();
return true;
}
else if(event.caller is randomButton) {
editor.construction.randomizeDesign();
return true;
}
else if(event.caller is configButton) {
randomConfig.prompt(this, editor.design);
return true;
}
else if(event.caller is undoButton) {
editor.data.undo();
editor.updateDesign();
return true;
}
else if(event.caller is redoButton) {
editor.data.redo();
editor.updateDesign();
return true;
}
else if(event.caller is paintButton) {
switchTool(paintTool);
}
else if(event.caller is replaceButton) {
switchTool(replaceTool);
}
else if(event.caller is moveButton) {
switchTool(moveTool);
}
else if(event.caller is dropButton) {
switchTool(dropTool);
}
else if(event.caller is clearButton) {
clearDesign();
}
}
if(event.caller is hullList && event.type == GUI_Changed) {
editor.updateDesign();
if(!SHOW_HULL_UNDERLAY && hullList.selected != 0) {
SHOW_HULL_UNDERLAY = true;
hullButton.pressed = true;
}
return true;
}
return BaseGuiElement::onGuiEvent(event);
}
void fillDesign(const Design@ design, bool group = true) {
if(group)
editor.data.startGroup();
for(uint i = 0, cnt = design.subsystemCount; i < cnt; ++i) {
auto@ sys = design.subsystems[i];
if(sys.type.isHull)
continue;
editor.data.act(CreateSubsystemAction(sys.type, sys.core, sys.direction));
for(uint j = 0, jcnt = sys.hexCount; j < jcnt; ++j) {
vec2u pos = sys.hexagon(j);
HexData hdat;
hdat.subsystem = i;
hdat.module = sys.module(j).index;
editor.data.act(PaintAction(pos, hdat));
}
}
if(group) {
editor.data.endGroup();
editor.updateDesign();
}
}
void clearDesign(bool group = true) {
if(group)
editor.data.startGroup();
for(int i = editor.data.subsystems.length - 1; i >= 0; --i) {
SubsystemData@ sdata = editor.data.subsystems[i];
for(int j = sdata.hexes.length - 1; j >= 0; --j)
editor.data.act(ClearAction(sdata.hexes[j]));
editor.data.act(RemoveSubsystemAction(i));
}
if(group) {
editor.data.endGroup();
editor.updateDesign();
}
}
void randomizeDesign(bool group = true) {
auto type = DT_Flagship;
auto ttag = editor.concept.getSelectedTypeTag();
if(ttag == "Support")
type = DT_Support;
else if(ttag == "Station")
type = DT_Station;
else if(ttag == "Satellite")
type = DT_Satellite;
double t = getExactTime();
//Designer rnd;
//rnd.prepare(type, editor.data.size, playerEmpire, "");
//randomConfig.apply(rnd);
//const Design@ dsg = rnd.design();
const Design@ dsg = createRandomDesign(type, editor.data.size, playerEmpire);
if(dsg is null)
return;
print("design: "+(getExactTime()-t)*1000.0+"ms");
if(group)
editor.data.startGroup();
clearDesign(group=false);
fillDesign(dsg, group=false);
if(editor.concept.nameBox.text.length == 0)
editor.concept.nameBox.text = dsg.name;
if(group) {
editor.data.endGroup();
editor.updateDesign();
}
}
void clearInvalid(bool group = true) {
if(group)
editor.data.startGroup();
string tagName = editor.concept.getSelectedTypeTag()+"Hull";
for(int i = editor.data.subsystems.length - 1; i >= 0; --i) {
SubsystemData@ sdata = editor.data.subsystems[i];
if(sdata.def.hasTag(tagName))
continue;
for(int j = sdata.hexes.length - 1; j >= 0; --j)
editor.data.act(ClearAction(sdata.hexes[j]));
editor.data.act(RemoveSubsystemAction(i));
}
if(group) {
editor.data.endGroup();
editor.updateDesign();
}
}
vec2i getCenterOffset(vec2i gridSize) {
vec2d minPos(gridSize), maxPos(0, 0);
for(int i = editor.data.subsystems.length - 1; i >= 0; --i) {
SubsystemData@ sdata = editor.data.subsystems[i];
for(int j = sdata.hexes.length - 1; j >= 0; --j) {
auto hex = getHexPosition(sdata.hexes[j]);
if(hex.x < minPos.x)
minPos.x = hex.x;
if(hex.y < minPos.y)
minPos.y = hex.y;
if(hex.x > maxPos.x)
maxPos.x = hex.x;
if(hex.y > maxPos.y)
maxPos.y = hex.y;
}
}
vec2d pSize = maxPos - minPos;
if(pSize.x <= 0 || pSize.y <= 0)
return vec2i();
vec2d shift = (vec2d(gridSize.x*0.75, gridSize.y) - pSize) / 2 - minPos;
shift.x /= 0.75;
vec2i offset = vec2i(shift);
if(offset.x % 2 != 0)
offset.x -= 1;
return offset;
}
void centerDesign(bool group = true) {
if(group)
editor.data.startGroup();
array<Action@> acts;
//Find the offsets
vec2i offset = getCenterOffset(editor.data.gridSize);
//Cache the new stuff
for(uint i = 0, cnt = editor.data.subsystems.length; i < cnt; ++i) {
SubsystemData@ sdata = editor.data.subsystems[i];
acts.insertLast(CreateSubsystemAction(sdata.def, sdata.core, direction=sdata.direction));
for(int j = sdata.hexes.length - 1; j >= 0; --j) {
auto hex = sdata.hexes[j];
auto@ hdat = editor.data.hex[hex];
if(hdat !is null)
acts.insertLast(PaintAction(vec2u(vec2i(hex) + offset), hdat));
}
}
//Clear old stuff
clearDesign(false);
//Apply new stuff
for(uint i = 0, cnt = acts.length; i < cnt; ++i)
editor.data.act(acts[i]);
if(group) {
editor.data.endGroup();
editor.updateDesign(true);
}
}
void changeSize(double newSize, bool group = true) {
editor.data.act(SizeChangeAction(editor.data.size, newSize));
editor.updateDesign(true);
editor.updateAbsolutePosition();
}
void changeGrid(vec2i newGrid, bool group = true) {
if(group)
editor.data.startGroup();
array<Action@> acts;
vec2i oldGrid = editor.data.gridSize;
//Find the offset
vec2d minPos(oldGrid), maxPos(0, 0);
for(int i = editor.data.subsystems.length - 1; i >= 0; --i) {
SubsystemData@ sdata = editor.data.subsystems[i];
for(int j = sdata.hexes.length - 1; j >= 0; --j) {
auto hex = getHexPosition(sdata.hexes[j]);
if(hex.x < minPos.x)
minPos.x = hex.x;
if(hex.y < minPos.y)
minPos.y = hex.y;
if(hex.x > maxPos.x)
maxPos.x = hex.x;
if(hex.y > maxPos.y)
maxPos.y = hex.y;
}
}
vec2d shift = (vec2d(newGrid.x*0.75, newGrid.y) - vec2d(oldGrid.x*0.75, oldGrid.y)) / 2;
shift.x = clamp(shift.x, 1.0 - minPos.x, double(newGrid.x)*0.75 - maxPos.x - 1);
shift.y = clamp(shift.y, 1.0 - minPos.y, newGrid.y - maxPos.y - 1);
shift.x /= 0.75;
vec2i offset = vec2i(shift);
if(offset.x % 2 != 0)
offset.x -= 1;
//Cache the new stuff
for(uint i = 0, cnt = editor.data.subsystems.length; i < cnt; ++i) {
SubsystemData@ sdata = editor.data.subsystems[i];
acts.insertLast(CreateSubsystemAction(sdata.def, sdata.core, direction=sdata.direction));
bool haveHexes = false;
for(int j = sdata.hexes.length - 1; j >= 0; --j) {
auto hex = sdata.hexes[j];
auto@ hdat = editor.data.hex[hex];
vec2u newhex = vec2u(vec2i(hex) + offset);
if(hdat !is null && newhex.x < uint(newGrid.x) && newhex.y < uint(newGrid.y)) {
acts.insertLast(PaintAction(newhex, hdat));
haveHexes = true;
}
}
if(!haveHexes)
acts.removeLast();
}
//Clear and resize
clearDesign(false);
editor.data.act(GridChangeAction(vec2u(oldGrid), vec2u(newGrid)));
//Apply new stuff
for(uint i = 0, cnt = acts.length; i < cnt; ++i)
editor.data.act(acts[i]);
if(group) {
editor.data.endGroup();
editor.updateDesign(true);
editor.updateAbsolutePosition();
}
}
vec2i drawError(const vec2i& pos, DesignError@ err) {
const Font@ fnt = skin.getFont(FT_Normal);
vec2i textSize = fnt.getDimension(err.text);
int width = textSize.x + 80;
Color col = warningColor;
if(err.fatal)
col = errorColor;
skin.draw(SS_Panel, SF_Normal, recti_area(pos, vec2i(width, 20)), col);
fnt.draw(pos + vec2i(5, 2), err.fatal ? locale::ERROR : locale::WARNING);
fnt.draw(pos + vec2i(75, 2), err.text);
return vec2i(width, 20);
}
void draw() {
vec2i topLeft = AbsolutePosition.topLeft;
vec2i botRight = AbsolutePosition.botRight;
////Filter bar
//skin.draw(SS_LightPanel, SF_Normal, recti(
// topLeft, vec2i(botRight.x, topLeft.y + 28)));
////Cost bar
//skin.draw(SS_LightPanel, SF_Normal, recti(
// vec2i(botRight.x - 700, topLeft.y),
// vec2i(botRight.x, topLeft.y + 28)));
//Subsystem bar
skin.draw(SS_PlainBox, SF_Normal, syslist.absolutePosition.padded(0,-8));
skin.draw(SS_PlainBox, SF_Normal, recti(
botRight.x-688-36-36-60, topLeft.y-2,
botRight.x-250, topLeft.y+35));
//Stat bar
skin.draw(SS_PlainBox, SF_Normal, recti(
vec2i(botRight.x - 258, topLeft.y),
vec2i(botRight.x - 4, botRight.y + 4)));
//Firing arcs
if(SHOW_ARC_UNDERLAY) {
for(uint i = 0, cnt = editor.design.subsystemCount; i < cnt; ++i) {
auto@ sys = editor.design.subsystems[i];
if(sys.type.hasTag(ST_Rotatable) && sys.effectorCount != 0)
display.drawFireArc(sys.core, 0.35f);
}
}
display.displayHull = SHOW_HULL_UNDERLAY;
display.displayHullWeights = SHOW_HULL_WEIGHTS;
hullList.visible = SHOW_HULL_UNDERLAY || hullList.selected != 0;
BaseGuiElement::draw();
clearClip();
uint errCnt = editor.design.errorCount;
//Draw error list
vec2i pos(topLeft.x + 271, topLeft.y+20);
DesignError@ hovErr = null;
for(uint i = 0; i < errCnt; ++i) {
DesignError@ err = editor.design.errors[i];
Color col = Color(0xff8000ff);
if(err.fatal)
col = colors::Red;
skin.getFont(FT_Subtitle).draw(pos=recti_area(pos, vec2i(size.width-500, 64)),
text=err.text, horizAlign=0.0, vertAlign=0.5,
color=col, stroke=colors::Black);
pos.y += 30;
}
//TODO:Draw the borders for errors
//for(uint i = 0; i < errCnt; ++i) {
//DesignError@ err = editor.design.errors[i];
////Use the correct color for the warning/error
//Color col = warningColor;
//if(err is hovErr)
//col = Color(0xffff00ff);
//else if(err.fatal)
//col = errorColor;
////Display subsystem errors
//if(err.subsys !is null) {
//display.drawSubsystemBorder(err.subsys, col, 4.0);
//}
////Display hex errors
//if(err.hex.x >= 0 && err.hex.y >= 0) {
//display.drawHexBorder(vec2u(err.hex), col, 6.0);
//}
//}
//Draw hovered error
if(display.hexHovered.x >= 0 && display.hexHovered.y >= 0) {
vec2u hov(display.hexHovered);
const Subsystem@ hovSys = editor.design.subsystem(hov);
vec2i pos = mousePos + vec2i(32, 0);
for(uint i = 0; i < errCnt; ++i) {
DesignError@ err = editor.design.errors[i];
if((hovSys is null || err.subsys !is hovSys) && err.hex != display.hexHovered)
continue;
vec2i size = drawError(pos, err);
pos.y += size.height + 4;
}
}
//Draw tool
activeTool.draw(editor.data, editor);
}
};
/** }}} */
/** Concept page {{{*/
class Concept : BaseGuiElement {
DesignEditor@ editor;
bool finalized = true;
GuiText@ nameLabel;
GuiTextbox@ nameBox;
GuiDropdown@ typeBox;
GuiText@ sizeLabel;
GuiTextbox@ sizeBox;
bool autoSize = false;
GuiText@ classLabel;
GuiDropdown@ classBox;
GuiSprite@ moneyIcon;
GuiText@ moneyValue;
GuiSprite@ laborIcon;
GuiText@ laborValue;
GuiButton@ roleButton;
GuiMarkupText@ roleText;
GuiSkinElement@ roleBox;
GuiPanel@ rolePanel;
array<GuiButton@> roleChoice;
GuiDropdown@ rangeChoice;
DesignSettings settings;
Concept(DesignEditor@ ed, Alignment@ align) {
@editor = ed;
super(editor, align);
updateAbsolutePosition();
@nameBox = GuiTextbox(this, Alignment(Left+145, Top+8, Left+500, Height=30));
nameBox.font = FT_Subtitle;
nameBox.tabIndex = 1;
nameBox.emptyText = locale::DSG_INSERT_NAME;
nameBox.setFilenameLimit();
@typeBox = GuiDropdown(this, Alignment(Left+145, Top+44, Left+322, Height=30));
typeBox.addItem(GuiListText(locale::DESIGN_FLAGSHIP, Sprite(spritesheet::AttributeIcons, 1, Color(0x00e5f7ff))));
typeBox.addItem(GuiListText(locale::DESIGN_SUPPORT, icons::ManageSupports));
typeBox.addItem(GuiListText(locale::DESIGN_STATION, Sprite(spritesheet::GuiOrbitalIcons, 0, Color(0x00e5f7ff))));
if(hasDLC("Heralds"))
typeBox.addItem(GuiListText(locale::DESIGN_SATELLITE, Sprite(spritesheet::GuiOrbitalIcons, 14, Color(0xe759ffff))));
@sizeLabel = GuiText(this, Alignment(Left+330, Top+44, Left+370, Height=30),
locale::DESIGN_SIZE_INPUT);
@sizeBox = GuiTextbox(this, Alignment(Left+370, Top+44, Left+500, Height=30));
sizeBox.font = FT_Subtitle;
sizeBox.tabIndex = 2;
@classLabel = GuiText(this, Alignment(Right-436, Top+9, Width=52, Height=30),
locale::DESIGN_CLASS_INPUT);
@classBox = GuiDropdown(this, Alignment(Right-436+52, Top+8, Right-140, Height=30));
classBox.font = FT_Subtitle;
classBox.tabIndex = 3;
@moneyIcon = GuiSprite(this, Alignment(Right-732-20, Top+42, Width=30, Height=30), icons::Money);
@moneyValue = GuiText(this, Alignment(Right-692-20, Top+44, Width=110, Height=28));
@laborIcon = GuiSprite(this, Alignment(Right-582-20, Top+42, Width=30, Height=30), icons::Labor);
@laborValue = GuiText(this, Alignment(Right-542-20, Top+44, Width=60, Height=28));
@roleButton = GuiButton(this, recti_area(vec2i(512, 8), vec2i(270, 66)));
roleButton.style = SS_AccordionHeader;
@roleText = GuiMarkupText(roleButton, Alignment().padded(8, 3));
@roleBox = GuiSkinElement(this, Alignment(Left+512-250, Top+74, Width=(270+500), Height=325), SS_Panel);
roleBox.visible = false;
roleBox.noClip = true;
@rolePanel = GuiPanel(roleBox, Alignment(Left+4, Top+4, Right-4, Bottom-40));
for(uint i = 0; i < SG_COUNT; ++i) {
GuiButton btn(rolePanel, recti_area(6+(i%2)*380, 4+(i/2)*92, 370, 88));
btn.style = SS_AccordionHeader;
GuiMarkupText txt(btn, Alignment().padded(8, 3));
roleChoice.insertLast(btn);
string roleName = SUPPORT_BEHAVIOR_NAMES[clamp(i, 0, SUPPORT_BEHAVIOR_NAMES.length-1)];
Sprite roleIcon = SUPPORT_BEHAVIOR_ICONS[clamp(i, 0, SUPPORT_BEHAVIOR_ICONS.length-1)];
txt.text = format("[img=$1;42][font=Subtitle]$2[/font]\n[i][color=#aaa]$3[/color][/i][/img]",
getSpriteDesc(roleIcon), localize("#BEH_"+roleName), localize("#BEH_"+roleName+"_DESC"));
}
GuiText rangeLabel(roleBox, Alignment(Left+0.5f-200, Bottom-40, Left+0.5f, Bottom-12));
rangeLabel.text = locale::SUPPORT_RANGE;
@rangeChoice = GuiDropdown(roleBox, Alignment(Left+0.5f, Bottom-40, Left+0.5f+200, Bottom-12));
rangeChoice.addItem(locale::RANGE_Auto);
rangeChoice.addItem(locale::RANGE_Far);
rangeChoice.addItem(locale::RANGE_Close);
}
void updateAbsolutePosition() {
if(roleButton !is null) {
if(size.width >= 1600)
roleButton.size = vec2i(270, 66);
else
roleButton.size = vec2i(270, 30);
}
BaseGuiElement::updateAbsolutePosition();
}
bool get_ready() {
if(nameBox.text.length == 0)
return false;
return true;
}
string getSelectedTypeTag() {
switch(typeBox.selected) {
case 0: return "Flagship";
case 1: return "Support";
case 2: return "Station";
case 3: return "Satellite";
}
return "Flagship";
}
void load(const Design@ dsg) {
update();
nameBox.text = dsg.name;
sizeBox.text = toString(dsg.size, 0);
if(dsg.hull.hasTag("Support"))
typeBox.selected = 1;
else if(dsg.hull.hasTag("Satellite")) {
if(typeBox.itemCount < 4)
typeBox.selected = 1;
else
typeBox.selected = 3;
}
else if(dsg.hull.hasTag("Station"))
typeBox.selected = 2;
else
typeBox.selected = 0;
for(uint i = 0, cnt = classBox.itemCount; i < cnt; ++i) {
if(playerEmpire.getDesignClass(i) is editor.targetClass) {
classBox.selected = i;
break;
}
}
if(dsg.settings !is null)
settings = cast<const DesignSettings>(dsg.settings);
else
settings = DesignSettings();
}
void generateDesign(DesignDescriptor& desc, bool checkHull = false) {
desc.name = nameBox.text;
//Find the most appropriate hull
auto@ elem = cast<HullElement>(editor.construction.hullList.selectedItem);
if(elem is null) {
desc.forceHull = false;
editor.data.forceHull = false;
string hullTag = getSelectedTypeTag();
if(desc.hull is null || checkHull || SHOW_HULL_UNDERLAY || hullTag != getHullTypeTag(desc.hull)) {
@desc.hull = editor.data.hull;
@desc.hull = getBestHull(desc, hullTag);
@editor.data.hull = desc.hull;
}
}
else {
desc.forceHull = true;
editor.data.forceHull = true;
@editor.data.hull = elem.hull;
@desc.hull = elem.hull;
}
}
void update() {
//Update classes
uint cnt = playerEmpire.designClassCount;
if(classBox.itemCount != cnt) {
classBox.clearItems();
classBox.selected = 0;
for(uint i = 0; i < cnt; ++i) {
const DesignClass@ cls = playerEmpire.getDesignClass(i);
classBox.addItem(cls.name);
if(cls is editor.targetClass)
classBox.selected = i;
}
}
//Size box validity
sizeBox.text = toString(editor.data.size, 0);
if(editor.data.size < editor.data.hull.minSize-0.001 || (editor.data.size > editor.data.hull.maxSize+0.001 && editor.data.hull.maxSize > 0))
sizeBox.bgColor = colors::Red;
else
sizeBox.bgColor = colors::White;
//Update cost
int buildCost = 0;
int maintainCost = 0;
double laborCost = 0;
getBuildCost(editor.design, buildCost, maintainCost, laborCost);
moneyValue.text = formatMoney(buildCost, maintainCost);
laborValue.text = standardize(laborCost, true);
roleButton.visible = editor.design.hasTag(ST_Support);
string roleName = SUPPORT_BEHAVIOR_NAMES[clamp(settings.behavior, 0, SUPPORT_BEHAVIOR_NAMES.length-1)];
Sprite roleIcon = SUPPORT_BEHAVIOR_ICONS[clamp(settings.behavior, 0, SUPPORT_BEHAVIOR_ICONS.length-1)];
if(size.width >= 1600) {
roleText.text = format("[img=$1;$4][vspace=$5]$3:\n[offset=15][font=Subtitle][b]$2[/b][/font][/offset][/vspace][/img]", getSpriteDesc(roleIcon), localize("#BEH_"+roleName), locale::SUPPORT_BEH,
toString(roleButton.size.height-6), toString(max((roleButton.size.height-50)/2, 0)));
}
else {
roleText.text = format("[img=$1;$4][vspace=$5][vspace=4]$3:[/vspace] [font=Subtitle][b]$2[/b][/font][/vspace][/img]", getSpriteDesc(roleIcon), localize("#BEH_"+roleName), locale::SUPPORT_BEH,
toString(roleButton.size.height-6), toString(max((roleButton.size.height-36)/2, 0)));
}
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
return BaseGuiElement::onMouseEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.type == GUI_Changed) {
if(event.caller is typeBox) {
if(!editor.design.hull.hasTag(getSelectedTypeTag())) {
editor.construction.clearInvalid();
editor.construction.clearBrush();
}
editor.data.forceHull = false;
editor.construction.hullList.selected = 0;
editor.updateDesign(checkHull=true);
editor.construction.updateHullList();
}
else if(event.caller is classBox) {
string value = classBox.getItem(classBox.selected);
const DesignClass@ cls = playerEmpire.getDesignClass(value, false);
if(cls !is null)
@editor.targetClass = cls;
editor.updateDesign();
}
else if(event.caller is nameBox) {
editor.updateDesign();
}
else if(event.caller is rangeChoice) {
settings.range = uint(rangeChoice.selected);
update();
setGuiFocus(roleBox);
}
return true;
}
else if(event.type == GUI_Clicked) {
if(event.caller is roleButton) {
roleBox.visible = !roleBox.visible;
if(roleBox.visible)
setGuiFocus(roleBox);
return true;
}
for(uint i = 0, cnt = roleChoice.length; i < cnt; ++i) {
if(event.caller is roleChoice[i]) {
settings.behavior = i;
roleBox.visible = false;
update();
return true;
}
}
}
else if(roleBox.visible && event.type == GUI_Focus_Lost && !roleBox.isAncestorOf(event.other)) {
if(roleButton.isAncestorOf(event.other) || event.other is rangeChoice.list || event.other is gui_root)
return false;
roleBox.visible = false;
return false;
}
else if(roleBox.visible && event.type == GUI_Focused && !roleBox.isAncestorOf(event.caller)) {
if(roleButton.isAncestorOf(event.caller) || event.caller is rangeChoice.list || event.caller is gui_root)
return false;
roleBox.visible = false;
return false;
}
else if(event.type == GUI_Confirmed || event.type == GUI_Focus_Lost) {
if(event.caller is sizeBox) {
autoSize = false;
double size = max(floor(toDouble(sizeBox.text)), 1.0);
if(size != editor.data.size)
editor.construction.changeSize(size);
}
else if(event.caller is nameBox) {
editor.updateDesign();
}
}
return BaseGuiElement::onGuiEvent(event);
}
void draw() {
BaseGuiElement::draw();
}
};
/** }}} */
/** Editor {{{*/
class DesignEditor : Tab {
const Design@ originalDesign;
const Design@ design;
const DesignClass@ targetClass;
DesignData data;
GuiButton@ duplicateButton;
GuiButton@ exportButton;
GuiButton@ saveButton;
GuiButton@ backButton;
Construction@ construction;
Concept@ concept;
DesignEditor() {
super();
//Set the keybind group to use
@keybinds = keybinds::DesignEditor;
//Create the subpanels
@concept = Concept(this, Alignment(Left, Top, Right, Top+78));
@construction = Construction(this, Alignment(Left, Top+78, Right, Bottom));
concept.bringToFront();
//Create the global buttons
@backButton = GuiButton(this, Alignment(Left+4, Top+10, Left+134, Height=60),
locale::BACK);
backButton.buttonIcon = icons::Back;
@duplicateButton = GuiButton(this, Alignment(Right-436, Top+43, Right-292, Height=30),
locale::DUPLICATE_DESIGN);
duplicateButton.buttonIcon = icons::Forward;
duplicateButton.color = Color(0xeeeeeeff);
@exportButton = GuiButton(this, Alignment(Right-288, Top+43, Right-140, Height=30),
locale::EXPORT_DESIGN);
exportButton.buttonIcon = icons::Export;
exportButton.color = Color(0xeeeeeeff);
@saveButton = GuiButton(this, Alignment(Right-134, Top+10, Right-4, Height=60),
locale::SAVE);
saveButton.buttonIcon = icons::Save;
saveButton.font = FT_Bold;
saveButton.color = Color(0x88ff88ff);
}
Color get_activeColor() {
return Color(0x83cfffff);
}
Color get_inactiveColor() {
return Color(0x009cffff);
}
Color get_seperatorColor() {
return Color(0x49738dff);
}
TabCategory get_category() {
return TC_Designs;
}
Sprite get_icon() {
return Sprite(material::TabDesigns);
}
void show() {
concept.update();
Tab::show();
}
void close() {
data.clear();
Tab::close();
}
void showDesignOverview() {
//Overview should be kept in previous
if(previous is null)
@previous = createDesignOverviewTab();
if(previous.category != TC_Designs) {
popTab(this);
}
else {
browseTab(this, previous, true);
data.clear();
}
}
void duplicate() {
if(originalDesign is null)
return;
concept.nameBox.text = uniqueDesignName(concept.nameBox.text, playerEmpire);
@originalDesign = null;
updateDesign();
concept.nameBox.focus(true);
}
void loadDesign(const Design@ dsg, bool isOriginal = true) {
//Display this design
if(isOriginal && dsg.used && dsg.owner is playerEmpire)
@originalDesign = dsg;
else
@originalDesign = null;
if(isOriginal || dsg.cls !is null) {
@targetClass = dsg.cls;
if(targetClass is null)
@targetClass = playerEmpire.getDesignClass(0);
}
title = dsg.name;
@design = dsg;
if(construction.display.hull !is dsg.hull) {
@construction.display.hull = dsg.hull;
updateAbsolutePosition();
}
construction.updateSubsystems();
@construction.display.design = dsg;
data.load(dsg);
concept.load(dsg);
construction.updateHullList();
construction.update();
concept.update();
updateProgress();
construction.clearBrush();
}
void loadDesign(const Hull@ hull, const DesignClass@ cls, const string& name, uint size) {
if(hull is null) {
if(cls !is null && cls.designCount != 0) {
ReadLock lock(playerEmpire.designMutex);
auto@ tDsg = cls.designs[0];
@hull = tDsg.hull;
}
else if(playerEmpire.shipset is null)
@hull = getHullDefinition(0);
else
@hull = playerEmpire.shipset.hulls[0];
}
if(cls is null)
@cls = playerEmpire.getDesignClass(0);
if(size < 1)
size = 1;
if(size < uint(hull.minSize))
size = hull.minSize;
@targetClass = cls;
DesignDescriptor desc;
@desc.hull = hull;
desc.size = size;
desc.gridSize = getDesignGridSize(desc.hull, desc.size);
desc.name = name;
title = name.length != 0 ? name : locale::CREATE_DESIGN;
const Design@ dsg = makeDesign(desc);
vec2u targGrid = getTargetGridSize(dsg);
if(targGrid != vec2u(dsg.hull.gridSize)) {
desc.gridSize = targGrid;
@dsg = makeDesign(desc);
}
loadDesign(makeDesign(desc), false);
construction.clearBrush();
concept.autoSize = true;
concept.nameBox.focus(true);
construction.updateSubsystems();
}
void saveDesign() {
const Design@ newDesign = generateDesign(checkHull=true);
newDesign.setSettings(getDesignSettings());
bool success = false;
const Design@ orig = originalDesign;
if(orig is null)
@orig = playerEmpire.getDesign(newDesign.name);
if(orig is null)
success = playerEmpire.addDesign(targetClass, newDesign);
else
success = playerEmpire.changeDesign(orig, newDesign, targetClass);
if(success) {
@originalDesign = newDesign;
concept.update();
}
}
DesignSettings@ getDesignSettings() {
return concept.settings;
}
void updateDesign(bool checkHull = false) {
@design = generateDesign(checkHull=checkHull);
if(data.gridSize != design.hull.gridSize) {
data.continueGroup();
construction.changeGrid(design.hull.gridSize, group = false);
data.endGroup();
updateDesign(true);
updateAbsolutePosition();
return;
}
@construction.display.design = design;
if(construction.display.hull !is design.hull) {
auto@ prevHull = construction.display.hull;
@construction.display.hull = design.hull;
if(getHullTypeTag(prevHull) != getHullTypeTag(design.hull)) {
construction.updateSubsystems();
updateAbsolutePosition();
}
}
saveButton.disabled = !concept.ready || !construction.ready
|| design.hasFatalErrors();
construction.update();
concept.update();
updateProgress();
if(data.posChange) {
data.posChange = false;
updateAbsolutePosition();
}
}
const Design@ generateDesign(bool checkHull = false) {
DesignDescriptor desc;
data.save(desc);
concept.generateDesign(desc, checkHull=checkHull);
const Design@ dsg = makeDesign(desc);
vec2u targGrid = getTargetGridSize(dsg);
if(targGrid != vec2u(dsg.hull.gridSize)) {
desc.gridSize = targGrid;
@dsg = makeDesign(desc);
}
return dsg;
}
void updateProgress() {
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
return BaseGuiElement::onMouseEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is saveButton) {
switch(event.type) {
case GUI_Clicked:
saveDesign();
if(!shiftKey)
showDesignOverview();
return true;
}
}
else if(event.caller is backButton) {
switch(event.type) {
case GUI_Clicked:
showDesignOverview();
return true;
}
}
else if(event.caller is duplicateButton) {
switch(event.type) {
case GUI_Clicked:
duplicate();
return true;
}
}
else if(event.caller is exportButton) {
switch(event.type) {
case GUI_Clicked:
updateDesign();
design.setSettings(getDesignSettings());
exportDesign(design, targetClass, this);
return true;
}
}
else if(event.type == GUI_Keybind_Down) {
switch(event.value) {
case KB_DESIGN_DUPLICATE:
duplicate();
break;
case KB_DESIGN_EXPORT:
updateDesign();
design.setSettings(getDesignSettings());
exportDesign(design, targetClass, this);
break;
case KB_DESIGN_UNDO:
if(construction.visible) {
data.undo();
updateDesign();
}
break;
case KB_DESIGN_REDO:
if(construction.visible) {
data.redo();
updateDesign();
}
break;
case KB_DESIGN_TOOL_PAINT:
if(construction.visible)
construction.switchTool(construction.paintTool);
break;
case KB_DESIGN_TOOL_REPLACE:
if(construction.visible)
construction.switchTool(construction.replaceTool);
break;
case KB_DESIGN_TOOL_MOVE:
if(construction.visible)
construction.switchTool(construction.moveTool);
break;
case KB_DESIGN_TOOL_DROP:
if(construction.visible)
construction.switchTool(construction.dropTool);
break;
case KB_DESIGN_SAVE:
if(saveButton.disabled)
sound::error.play(priority=true);
else
saveDesign();
break;
case KB_DESIGN_SAVE_CLOSE:
if(saveButton.disabled)
sound::error.play(priority=true);
else {
saveDesign();
showDesignOverview();
}
break;
}
}
else if(event.type == GUI_Clicked) {
}
return BaseGuiElement::onGuiEvent(event);
}
void draw() {
//Draw the global background
skin.draw(SS_DesignEditorBG, SF_Normal, AbsolutePosition);
//Top bar
vec2i topLeft = AbsolutePosition.topLeft;
vec2i botRight = AbsolutePosition.botRight;
Color color;
if(design !is null)
color = design.color;
skin.draw(SS_PlainOverlay, SF_Normal, recti(
topLeft-vec2i(10,12), vec2i(botRight.x+10, topLeft.y + 78)),
color);
BaseGuiElement::draw();
}
};
Tab@ createDesignEditorTab() {
return DesignEditor();
}
Tab@ createDesignEditorTab(const Design@ dsg) {
DesignEditor tab;
tab.loadDesign(dsg);
return tab;
}
void loadDesignEditor(Tab@ tab, const Design@ dsg) {
cast<DesignEditor@>(tab).loadDesign(dsg);
}
void loadDesignEditor(Tab@ tab, const Hull@ hull, const DesignClass@ cls, const string& name, uint size) {
cast<DesignEditor@>(tab).loadDesign(hull, cls, name, size);
}
void preReload(Message& msg) {
const Design@ dsg;
auto@ tab = cast<DesignEditor>(ActiveTab);
if(tab !is null)
@dsg = tab.originalDesign;
msg << dsg;
}
void postReload(Message& msg) {
const Design@ dsg;
msg >> dsg;
if(dsg !is null)
browseTab(ActiveTab, createDesignEditorTab(dsg));
}
/** }}} */