1334 lines
32 KiB
ActionScript
1334 lines
32 KiB
ActionScript
import systems;
|
|
import navigation.SmartCamera;
|
|
from navigation.elevation import getElevationIntersect, getElevation;
|
|
from input import activeCamera;
|
|
import ftl;
|
|
import tabs.Tab;
|
|
import orbitals;
|
|
import orders;
|
|
import resources;
|
|
import abilities;
|
|
import buildings;
|
|
from hooks import Hook;
|
|
from generic_effects import AddTurret, GenericRepeatHook, ShowsRange, IfHook;
|
|
from targeting.targeting import hoverFilter, lastHoverFilter;
|
|
|
|
bool debugCursor = false;
|
|
double get_DOUBLECLICK_TIME() {
|
|
return double(settings::iDoubleClickMS) / 1000.0;
|
|
}
|
|
const double HOVER_CONE_SLOPE = 0.02;
|
|
|
|
import void targetMovement() from "targeting.MoveTarget";
|
|
import bool canMove(Object& obj) from "targeting.MoveTarget";
|
|
import Tab@ get_ActiveTab() from "tabs.tabbar";
|
|
import bool isGuiHovered() from "gui";
|
|
import void pinObject(Tab@ _tab, Object@ obj, bool floating) from "tabs.GalaxyTab";
|
|
import void zoomTabTo(Object@ obj) from "tabs.GalaxyTab";
|
|
import void openOverlay(Object@ obj) from "tabs.GalaxyTab";
|
|
import void openSupportOverlay(Object@ obj) from "tabs.GalaxyTab";
|
|
|
|
set_int selectedIDs;
|
|
array<Object@> selection;
|
|
array<array<Object@>> groups(9);
|
|
Object@ hovered;
|
|
Object@ uiobj;
|
|
|
|
vec2i lastMousePos;
|
|
|
|
Object@ get_hoveredObject() {
|
|
if(lastMousePos != mousePos || hoverFilter !is lastHoverFilter)
|
|
updateHoveredObject();
|
|
return hovered;
|
|
}
|
|
|
|
Object@ get_uiObject() {
|
|
return uiobj;
|
|
}
|
|
|
|
void set_uiObject(Object@ obj) {
|
|
@uiobj = obj;
|
|
}
|
|
|
|
array<Object@>@ get_selectedObjects() {
|
|
return selection;
|
|
}
|
|
|
|
Object@ get_selectedObject() {
|
|
if(selection.length == 0)
|
|
return null;
|
|
return selection[0];
|
|
}
|
|
|
|
bool isSelected(Object@ obj) {
|
|
return selectedIDs.contains(obj.id);
|
|
}
|
|
|
|
void selectObject(Object@ obj) {
|
|
if(ctrlKey)
|
|
toggleObjectSelect(obj);
|
|
else
|
|
selectObject(obj, shiftKey);
|
|
}
|
|
|
|
void deselectObject(Object@ obj) {
|
|
obj.selected = false;
|
|
selection.remove(obj);
|
|
selectedIDs.erase(obj.id);
|
|
updateBeams();
|
|
}
|
|
|
|
void clearSelection() {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i)
|
|
selection[i].selected = false;
|
|
selection.length = 0;
|
|
selectedIDs.clear();
|
|
}
|
|
|
|
void addToSelection(Object@ obj) {
|
|
if(!selectedIDs.contains(obj.id)) {
|
|
obj.selected = true;
|
|
selection.insertLast(obj);
|
|
selectedIDs.insert(obj.id);
|
|
}
|
|
}
|
|
|
|
void toggleObjectSelect(Object@ obj) {
|
|
if(obj is null)
|
|
return;
|
|
if(isSelected(obj))
|
|
deselectObject(obj);
|
|
else
|
|
selectObject(obj, true);
|
|
}
|
|
|
|
void selectObject(Object@ obj, bool add) {
|
|
if(obj is null) {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i)
|
|
selection[i].selected = false;
|
|
selection.length = 0;
|
|
selectedIDs.clear();
|
|
}
|
|
else if(!add) {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i)
|
|
selection[i].selected = false;
|
|
if(ctrlKey) {
|
|
selection.length = 1;
|
|
@selection[0] = obj;
|
|
obj.selected = true;
|
|
selectedIDs.clear();
|
|
selectedIDs.insert(obj.id);
|
|
}
|
|
else {
|
|
if(obj !is null) {
|
|
obj.selected = true;
|
|
selection.length = 1;
|
|
@selection[0] = obj;
|
|
selectedIDs.clear();
|
|
selectedIDs.insert(obj.id);
|
|
}
|
|
else {
|
|
selection.length = 0;
|
|
selectedIDs.clear();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(ctrlKey) {
|
|
if(!selectedIDs.contains(obj.id)) {
|
|
obj.selected = true;
|
|
selection.insertLast(obj);
|
|
selectedIDs.insert(obj.id);
|
|
}
|
|
}
|
|
else {
|
|
if(obj !is null && !selectedIDs.contains(obj.id)) {
|
|
obj.selected = true;
|
|
selection.insertLast(obj);
|
|
selectedIDs.insert(obj.id);
|
|
}
|
|
}
|
|
}
|
|
updateBeams();
|
|
}
|
|
|
|
int getSelectionScale() {
|
|
int scale = 0;
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Ship@ ship = cast<Ship>(selection[i]);
|
|
if(ship is null)
|
|
continue;
|
|
int shipScale = ship.blueprint.design.size;
|
|
scale += shipScale;
|
|
}
|
|
return scale;
|
|
}
|
|
|
|
vec3d getSelectionPosition(bool onlyMovable = false) {
|
|
vec3d pos;
|
|
uint j = 0;
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(onlyMovable) {
|
|
if(!obj.hasMover || !obj.owner.controlled)
|
|
continue;
|
|
}
|
|
pos += selection[i].position;
|
|
++j;
|
|
}
|
|
if(j != 0)
|
|
pos /= j;
|
|
return pos;
|
|
}
|
|
|
|
Object@ pressedObject;
|
|
double pressedTime;
|
|
|
|
void selectionClick(uint button, bool pressed) {
|
|
Object@ Hovered = hoveredObject;
|
|
bool doubleClicked = false;
|
|
|
|
//Send clicks to the tab
|
|
if(!pressed) {
|
|
//Check for double clicks
|
|
if(button == 0 && Hovered !is null) {
|
|
sound::objselect.play(priority=true);
|
|
double now = frameTime;
|
|
if(pressedObject is Hovered) {
|
|
if(pressedTime > now - DOUBLECLICK_TIME) {
|
|
doubleClicked = true;
|
|
@pressedObject = null;
|
|
}
|
|
}
|
|
else {
|
|
@pressedObject = Hovered;
|
|
}
|
|
pressedTime = now;
|
|
}
|
|
|
|
//Check for pings
|
|
if(altKey && activeCamera !is null && button == 0) {
|
|
vec3d pingPos;
|
|
if(Hovered !is null)
|
|
pingPos = Hovered.node_position;
|
|
else
|
|
pingPos = activeCamera.screenToPoint(mousePos);
|
|
uint pingType = 0;
|
|
if(ctrlKey)
|
|
pingType = 1;
|
|
if(shiftKey)
|
|
sendPingAll(pingPos, pingType);
|
|
else
|
|
sendPingAllied(pingPos, pingType);
|
|
return;
|
|
}
|
|
|
|
Tab@ activeTab = ActiveTab;
|
|
if(activeTab !is null && Hovered !is null)
|
|
if(activeTab.objectInteraction(Hovered, button, doubleClicked))
|
|
return;
|
|
|
|
//Actual object selection
|
|
if(button == 0) {
|
|
if(Hovered !is null || !(ctrlKey || shiftKey)) {
|
|
if(ctrlKey)
|
|
toggleObjectSelect(Hovered);
|
|
else
|
|
selectObject(Hovered, shiftKey);
|
|
}
|
|
}
|
|
}
|
|
//Go into movement targeting mode
|
|
else if(button == 1 && Hovered is null) {
|
|
if(selection.length != 0) {
|
|
bool hasMovement = false;
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(canMove(obj)) {
|
|
hasMovement = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(hasMovement)
|
|
targetMovement();
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SelectionType {
|
|
ST_Military,
|
|
ST_Civilian,
|
|
ST_Fleets,
|
|
ST_Planets,
|
|
ST_Other
|
|
};
|
|
|
|
SelectionType classifyRestrictive(array<Object@>& objs) {
|
|
uint type = ST_Other;
|
|
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(!obj.owner.controlled)
|
|
continue;
|
|
|
|
uint newType = ST_Other;
|
|
switch(obj.type) {
|
|
case OT_Ship:
|
|
//Nothing overrides ship selection
|
|
if(obj.hasLeaderAI) {
|
|
if(obj.getFleetStrength() < 1000.0)
|
|
newType = ST_Civilian;
|
|
else
|
|
newType = ST_Military;
|
|
}
|
|
break;
|
|
case OT_Planet:
|
|
newType = ST_Planets;
|
|
break;
|
|
}
|
|
|
|
if(newType < type)
|
|
type = newType;
|
|
}
|
|
|
|
return SelectionType(type);
|
|
}
|
|
|
|
SelectionType classifyRelaxed(array<Object@>& objs) {
|
|
SelectionType type = ST_Fleets;
|
|
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(!obj.owner.controlled)
|
|
return ST_Other;
|
|
|
|
switch(obj.type) {
|
|
case OT_Ship:
|
|
//Nothing overrides ship selection
|
|
if(!obj.hasLeaderAI)
|
|
return ST_Other;
|
|
break;
|
|
case OT_Planet:
|
|
if(type == ST_Fleets)
|
|
type = ST_Planets;
|
|
break;
|
|
default:
|
|
return ST_Other;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
void filter(array<Object@>& objs, SelectionType type) {
|
|
if(type == ST_Other)
|
|
return;
|
|
|
|
array<Object@> output;
|
|
|
|
switch(type) {
|
|
case ST_Fleets:
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(!obj.owner.controlled || !obj.isShip || !obj.hasLeaderAI)
|
|
continue;
|
|
output.insertLast(obj);
|
|
}
|
|
break;
|
|
case ST_Civilian:
|
|
case ST_Military:
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(!obj.owner.controlled || !obj.isShip || !obj.hasLeaderAI)
|
|
continue;
|
|
if((obj.getFleetStrength() < 1000.0) != (type == ST_Civilian))
|
|
continue;
|
|
output.insertLast(obj);
|
|
}
|
|
break;
|
|
case ST_Planets:
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(!obj.owner.controlled || !obj.isPlanet)
|
|
continue;
|
|
output.insertLast(obj);
|
|
}
|
|
break;
|
|
}
|
|
|
|
objs = output;
|
|
}
|
|
|
|
|
|
void dragSelect(const recti& box) {
|
|
array<Object@> objects = activeCamera.camera.boxSelect(box);
|
|
|
|
SelectionType selType;
|
|
|
|
//If we're adding to our current selection, filter by the current selection type
|
|
if(shiftKey)
|
|
selType = classifyRelaxed(selection);
|
|
else
|
|
selType = classifyRestrictive(objects);
|
|
|
|
filter(objects, selType);
|
|
|
|
if(!shiftKey)
|
|
clearSelection();
|
|
for(uint i = 0, cnt = objects.length; i < cnt; ++i)
|
|
addToSelection(objects[i]);
|
|
|
|
updateBeams();
|
|
}
|
|
|
|
/*double getObjectMaxSelectionDistance(Object& obj) {
|
|
switch(obj.type) {
|
|
case OT_Ship:
|
|
if(obj.hasLeaderAI)
|
|
return INFINITY;
|
|
else
|
|
return 300.0 * obj.radius;
|
|
case OT_Planet:
|
|
return 16000.0;
|
|
case OT_Orbital:
|
|
return 16000.0;
|
|
case OT_Asteroid:
|
|
case OT_Anomaly:
|
|
case OT_Pickup:
|
|
return 16000.0;
|
|
case OT_ColonyShip:
|
|
return 2000.0;
|
|
case OT_Star:
|
|
return INFINITY;
|
|
}
|
|
return 16000.0;
|
|
}*/
|
|
|
|
const double MAX_SUPPORT_SELECT_DIST = 1000.0;
|
|
const double MAX_PLANET_SELECT_DIST = 52000.0;
|
|
const double MAX_PLANET_PHYS_SEL_DIST = 2000.0;
|
|
const double MAX_ORBITAL_SELECT_DIST = 52000.0;
|
|
const double MAX_PICKUP_SELECT_DIST = 20000.0;
|
|
const double MAX_COLSHIP_SELECT_DIST = 12000.0;
|
|
const double MAX_CIVILIAN_SELECT_DIST = 1500.0;
|
|
|
|
array<Node@> nodes, bestNodes;
|
|
|
|
void updateHoveredObject() {
|
|
//double start = getExactTime();
|
|
lastMousePos = mousePos;
|
|
@lastHoverFilter = hoverFilter;
|
|
|
|
//Outside of the screen, nothing to do
|
|
if(lastMousePos.x < 0 || lastMousePos.y < 0 || lastMousePos.x > screenSize.x || lastMousePos.y > screenSize.y
|
|
|| isGuiHovered()) {
|
|
@hovered = null;
|
|
}
|
|
else if(activeCamera !is null) {
|
|
line3dd line = activeCamera.screenToRay(lastMousePos);
|
|
nodeConeSelect(line, HOVER_CONE_SLOPE, nodes);
|
|
|
|
//Find best node
|
|
Node@ best = null;
|
|
double bestScore = 0;
|
|
for(uint i = 0, cnt = nodes.length; i < cnt; ++i) {
|
|
Node@ cur = nodes[i];
|
|
Object@ obj = cur.object;
|
|
if(obj is null)
|
|
continue;
|
|
|
|
double dist = cur.position.distanceTo(line.start) * config::GFX_DISTANCE_MOD;
|
|
double score = 1.0 / dist;
|
|
Empire@ owner = obj.owner;
|
|
|
|
if(hoverFilter is null) {
|
|
if(obj.selected)
|
|
score /= 10.0;
|
|
else if(owner.controlled)
|
|
score *= 1.5;
|
|
}
|
|
else {
|
|
if(!hoverFilter(obj))
|
|
score /= 10.0;
|
|
}
|
|
|
|
switch(obj.type) {
|
|
case OT_Ship:
|
|
if(!obj.hasLeaderAI) {
|
|
if(dist > MAX_SUPPORT_SELECT_DIST)
|
|
continue;
|
|
score /= 4.0;
|
|
}
|
|
if(owner !is null && !owner.major)
|
|
score /= 10.0;
|
|
break;
|
|
case OT_Planet:
|
|
if(dist > MAX_PLANET_SELECT_DIST)
|
|
continue;
|
|
//Ignore physical planets at great distances, the player will be selecting the icon instead
|
|
if(dist > MAX_PLANET_PHYS_SEL_DIST / 10.0 * obj.radius && cast<PlanetIconNode>(cur) is null)
|
|
continue;
|
|
break;
|
|
case OT_Orbital:
|
|
if(dist > MAX_ORBITAL_SELECT_DIST)
|
|
continue;
|
|
score /= 2.0;
|
|
break;
|
|
case OT_Anomaly:
|
|
case OT_Asteroid:
|
|
score /= 3.0;
|
|
break;
|
|
case OT_Pickup:
|
|
break;
|
|
case OT_ColonyShip:
|
|
if(dist > MAX_COLSHIP_SELECT_DIST)
|
|
continue;
|
|
score /= 20.0;
|
|
break;
|
|
case OT_Civilian:
|
|
if(dist > MAX_CIVILIAN_SELECT_DIST * obj.radius)
|
|
continue;
|
|
score /= 5.0;
|
|
break;
|
|
case OT_Star:
|
|
default:
|
|
score /= 6.0;
|
|
break;
|
|
}
|
|
|
|
if(score * 4.0 < bestScore)
|
|
continue;
|
|
|
|
score /= cur.position.distanceTo(line.getClosestPoint(cur.position, true));
|
|
if(score < bestScore)
|
|
continue;
|
|
|
|
@best = cur;
|
|
bestScore = score;
|
|
}
|
|
|
|
//Set hover
|
|
if(best !is null)
|
|
@hovered = best.object;
|
|
else
|
|
@hovered = null;
|
|
|
|
//Update beam shown for hovered objects
|
|
updateHoverBeams();
|
|
|
|
bestNodes.length = 0;
|
|
nodes.length = 0;
|
|
}
|
|
|
|
//double end = getExactTime();
|
|
//error("Hovered update took " + int((end - start) * 1000.0) + "ms");
|
|
}
|
|
|
|
void clearHoveredObject() {
|
|
@hovered = null;
|
|
}
|
|
|
|
class DebugCursorCommand : ConsoleCommand {
|
|
void execute(const string& args) {
|
|
debugCursor = args.length == 0 || toBool(args);
|
|
}
|
|
};
|
|
|
|
void pinObj(bool pressed) {
|
|
if(!pressed) {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(obj !is null)
|
|
pinObject(ActiveTab, obj, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pinObjFloat(bool pressed) {
|
|
if(!pressed) {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(obj !is null)
|
|
pinObject(ActiveTab, obj, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void holdPosition(bool pressed) {
|
|
if(!pressed) {
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(obj.hasLeaderAI)
|
|
obj.setHoldPosition(!shiftKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
void group_key(bool pressed, uint groupIndex) {
|
|
if(!pressed)
|
|
return;
|
|
|
|
array<Object@>@ group = groups[groupIndex];
|
|
if(ctrlKey && shiftKey) {
|
|
//Add current selection to group
|
|
if(selection.length == 0)
|
|
return;
|
|
|
|
set_int groupObjs;
|
|
for(uint i = 0, cnt = group.length; i < cnt; ++i)
|
|
groupObjs.insert(group[i].id);
|
|
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
|
Object@ obj = selection[i];
|
|
if(groupObjs.contains(obj.id))
|
|
continue;
|
|
group.insertLast(obj);
|
|
groupObjs.insert(obj.id);
|
|
}
|
|
|
|
sound::objselect.play(priority=true);
|
|
}
|
|
else if(shiftKey) {
|
|
//Add group to selection
|
|
for(uint i = 0, cnt = group.length; i < cnt; ++i)
|
|
addToSelection(group[i]);
|
|
|
|
if(group.length != 0)
|
|
sound::objselect.play(priority=true);
|
|
}
|
|
else if(ctrlKey) {
|
|
//Set group to selection
|
|
if(selection.length != 0) {
|
|
group = selection;
|
|
sound::objselect.play(priority=true);
|
|
}
|
|
}
|
|
else {
|
|
//Switch to selection
|
|
// If the group is already (fully) selected, instead move the camera to that group
|
|
bool sameGroup = false;
|
|
if(selection.length == group.length) {
|
|
sameGroup = true;
|
|
for(uint i = 0, cnt = group.length; i < cnt; ++i) {
|
|
if(!isSelected(group[i])) {
|
|
sameGroup = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(sameGroup) {
|
|
if(selection.length != 0)
|
|
zoomTabTo(selection[0]);
|
|
}
|
|
else {
|
|
clearSelection();
|
|
for(uint i = 0, cnt = group.length; i < cnt; ++i)
|
|
addToSelection(group[i]);
|
|
sound::objselect.play(priority=true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void group_1(bool pressed) { group_key(pressed, 0); }
|
|
void group_2(bool pressed) { group_key(pressed, 1); }
|
|
void group_3(bool pressed) { group_key(pressed, 2); }
|
|
void group_4(bool pressed) { group_key(pressed, 3); }
|
|
void group_5(bool pressed) { group_key(pressed, 4); }
|
|
void group_6(bool pressed) { group_key(pressed, 5); }
|
|
void group_7(bool pressed) { group_key(pressed, 6); }
|
|
void group_8(bool pressed) { group_key(pressed, 7); }
|
|
void group_9(bool pressed) { group_key(pressed, 8); }
|
|
|
|
bool SHOW_FIREARCS = false;
|
|
void toggleFireArcs(bool pressed) {
|
|
if(!pressed)
|
|
SHOW_FIREARCS = !SHOW_FIREARCS;
|
|
}
|
|
|
|
void manage_obj(bool pressed) {
|
|
if(!pressed) {
|
|
if(selectedObject !is null)
|
|
openOverlay(selectedObject);
|
|
else if(hoveredObject !is null)
|
|
openOverlay(hoveredObject);
|
|
}
|
|
}
|
|
|
|
void manage_support(bool pressed) {
|
|
if(!pressed) {
|
|
if(selectedObject !is null)
|
|
openSupportOverlay(selectedObject);
|
|
else if(hoveredObject !is null)
|
|
openSupportOverlay(hoveredObject);
|
|
}
|
|
}
|
|
|
|
void init() {
|
|
addConsoleCommand("debug_cursor", DebugCursorCommand());
|
|
keybinds::Global.addBind(KB_MANAGE, "manage_obj");
|
|
keybinds::Global.addBind(KB_MANAGE_SUPPORT, "manage_support");
|
|
keybinds::Global.addBind(KB_PIN, "pinObj");
|
|
keybinds::Global.addBind(KB_HOLD_POSITION, "holdPosition");
|
|
keybinds::Global.addBind(KB_PIN_FLOATING, "pinObjFloat");
|
|
keybinds::Global.addBind(KB_TOGGLE_FIREARCS, "toggleFireArcs");
|
|
hoverBeams.isHover = true;
|
|
|
|
keybinds::Global.addBind(KB_GROUP_1, "group_1");
|
|
keybinds::Global.addBind(KB_GROUP_2, "group_2");
|
|
keybinds::Global.addBind(KB_GROUP_3, "group_3");
|
|
keybinds::Global.addBind(KB_GROUP_4, "group_4");
|
|
keybinds::Global.addBind(KB_GROUP_5, "group_5");
|
|
keybinds::Global.addBind(KB_GROUP_6, "group_6");
|
|
keybinds::Global.addBind(KB_GROUP_7, "group_7");
|
|
keybinds::Global.addBind(KB_GROUP_8, "group_8");
|
|
keybinds::Global.addBind(KB_GROUP_9, "group_9");
|
|
|
|
keybinds::Global.addBind(KB_PING, "ping_allied");
|
|
keybinds::Global.addBind(KB_PING_ALL, "ping_all");
|
|
}
|
|
|
|
void ping_allied(bool pressed) {
|
|
if(pressed)
|
|
return;
|
|
|
|
vec3d pingPos;
|
|
if(hoveredObject !is null)
|
|
pingPos = hoveredObject.node_position;
|
|
else if(activeCamera !is null)
|
|
pingPos = activeCamera.screenToPoint(mousePos);
|
|
else
|
|
return;
|
|
|
|
uint pingType = 0;
|
|
if(ctrlKey)
|
|
pingType = 1;
|
|
|
|
sendPingAllied(pingPos, pingType);
|
|
}
|
|
|
|
void ping_all(bool pressed) {
|
|
if(pressed)
|
|
return;
|
|
|
|
vec3d pingPos;
|
|
if(hoveredObject !is null)
|
|
pingPos = hoveredObject.node_position;
|
|
else if(activeCamera !is null)
|
|
pingPos = activeCamera.screenToPoint(mousePos);
|
|
else
|
|
return;
|
|
|
|
uint pingType = 0;
|
|
if(ctrlKey)
|
|
pingType = 1;
|
|
|
|
sendPingAll(pingPos, pingType);
|
|
}
|
|
|
|
const Color MOVE_BEAM_COLOR(0x00ff00ff);
|
|
const Color RALLY_BEAM_COLOR(0x00ffaaff);
|
|
const Color FTL_BEAM_COLOR(0x00c0ffff);
|
|
const Color HEIGHT_BEAM_COLOR(0xaaaaaaff);
|
|
const Color IMPORT_BEAM_COLOR(0x0000ffff);
|
|
const Color EXPORT_BEAM_COLOR(0x76e0e0ff);
|
|
const Color QUEUED_EXPORT_BEAM_COLOR(0xffe400ff);
|
|
const Color QUEUED_IMPORT_BEAM_COLOR(0xffe400ff);
|
|
const Color LINK_BEAM_COLOR(0xff00bbff);
|
|
const Color PROJECT_BEAM_COLOR(0xdcc35fff);
|
|
const Color TRADE_LINK_BEAM_COLOR(0x888888ff);
|
|
|
|
const Color DISABLED_BEAM_COLOR(0xff0000ff);
|
|
const Color DISABLED_BEAM_COLOR2(0xff9000ff);
|
|
|
|
vec3d strategicPosition(Object& obj) {
|
|
if(obj.isPlanet)
|
|
return obj.planetIconPosition;
|
|
if(obj.isAsteroid)
|
|
return cast<Asteroid>(obj).strategicIconPosition;
|
|
if(obj.isOddity)
|
|
return cast<Oddity>(obj).strategicIconPosition;
|
|
if(obj.isOrbital)
|
|
return cast<Orbital>(obj).strategicIconPosition;
|
|
return obj.node_position;
|
|
}
|
|
|
|
class BEAMS {
|
|
BeamNode@ heightBeam;
|
|
BeamNode@ linkBeam;
|
|
BeamNode@ projectBeam;
|
|
BeamNode@ rallyBeam;
|
|
array<BeamNode@> moveBeams;
|
|
array<BeamNode@> resourceBeams;
|
|
array<PlaneNode@> ranges;
|
|
bool primary = false;
|
|
bool isHover = false;
|
|
|
|
Object@ cacheObj;
|
|
array<OrbitalSection>@ sectionCache;
|
|
double sectCacheTime = -INFINITY;
|
|
array<const BuildingType@>@ bldCache;
|
|
double bldCacheTime = -INFINITY;
|
|
|
|
BeamNode@ makeBeam(const Color& color) {
|
|
BeamNode@ beam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
|
beam.visible = false;
|
|
beam.color = color;
|
|
return beam;
|
|
}
|
|
|
|
void updateBeam(BeamNode@& node, const vec3d& from, const vec3d& to,
|
|
const Color& color) {
|
|
BeamNode@ check = node;
|
|
if(check is null) {
|
|
@check = makeBeam(color);
|
|
@node = check;
|
|
}
|
|
check.position = from;
|
|
check.rebuildTransform();
|
|
check.endPosition = to;
|
|
check.color = color;
|
|
check.visible = true;
|
|
}
|
|
|
|
void hideBeam(BeamNode@& node) {
|
|
BeamNode@ check = node;
|
|
if(check !is null) {
|
|
check.visible = false;
|
|
check.markForDeletion();
|
|
@node = null;
|
|
}
|
|
}
|
|
|
|
PlaneNode@ makePlane(const Color& color, const Material@ mat = material::RangeCircle) {
|
|
PlaneNode@ plane = PlaneNode(mat, 0.0);
|
|
plane.visible = false;
|
|
plane.color = color;
|
|
return plane;
|
|
}
|
|
|
|
void updatePlane(PlaneNode@& node, const vec3d& pos, double size,
|
|
const Color& color, const Material@ mat = material::RangeCircle) {
|
|
PlaneNode@ check = node;
|
|
if(check is null) {
|
|
@check = makePlane(color, mat);
|
|
@node = check;
|
|
}
|
|
check.position = pos;
|
|
check.scale = size;
|
|
check.rebuildTransform();
|
|
check.color = color;
|
|
@check.material = mat;
|
|
check.visible = true;
|
|
}
|
|
|
|
void hidePlane(PlaneNode@& node) {
|
|
PlaneNode@ check = node;
|
|
if(check !is null) {
|
|
check.visible = false;
|
|
check.markForDeletion();
|
|
@node = null;
|
|
}
|
|
}
|
|
|
|
void remove() {
|
|
if(heightBeam !is null)
|
|
heightBeam.markForDeletion();
|
|
if(linkBeam !is null)
|
|
linkBeam.markForDeletion();
|
|
if(projectBeam !is null)
|
|
projectBeam.markForDeletion();
|
|
if(rallyBeam !is null)
|
|
rallyBeam.markForDeletion();
|
|
for(uint i = 0, cnt = resourceBeams.length; i < cnt; ++i)
|
|
if(resourceBeams[i] !is null)
|
|
resourceBeams[i].markForDeletion();
|
|
for(uint i = 0, cnt = moveBeams.length; i < cnt; ++i)
|
|
if(moveBeams[i] !is null)
|
|
moveBeams[i].markForDeletion();
|
|
for(uint i = 0, cnt = ranges.length; i < cnt; ++i)
|
|
if(ranges[i] !is null)
|
|
ranges[i].markForDeletion();
|
|
}
|
|
|
|
void hideAll() {
|
|
hideBeam(heightBeam);
|
|
hideBeam(linkBeam);
|
|
hideBeam(projectBeam);
|
|
hideBeam(rallyBeam);
|
|
for(uint i = 0, cnt = resourceBeams.length; i < cnt; ++i)
|
|
hideBeam(resourceBeams[i]);
|
|
resourceBeams.length = 0;
|
|
for(uint i = 0, cnt = moveBeams.length; i < cnt; ++i)
|
|
hideBeam(moveBeams[i]);
|
|
moveBeams.length = 0;
|
|
for(uint i = 0, cnt = ranges.length; i < cnt; ++i)
|
|
hidePlane(ranges[i]);
|
|
ranges.length = 0;
|
|
}
|
|
|
|
void addBeam(array<BeamNode@>& beams, uint& index, const vec3d& from, const vec3d& to,
|
|
const Color& color) {
|
|
if(beams.length <= index) {
|
|
BeamNode@ node;
|
|
updateBeam(node, from, to, color);
|
|
beams.insertLast(node);
|
|
}
|
|
else {
|
|
updateBeam(beams[index], from, to, color);
|
|
}
|
|
index += 1;
|
|
}
|
|
|
|
void truncateBeams(array<BeamNode@>& beams, uint index) {
|
|
uint cnt = beams.length;
|
|
for(uint i = index; i < cnt; ++i)
|
|
hideBeam(beams[i]);
|
|
beams.length = index;
|
|
}
|
|
|
|
PlaneNode@ addPlane(array<PlaneNode@>& planes, uint& index, const vec3d& pos, double size,
|
|
const Color& color, const Material@ mat = material::RangeCircle) {
|
|
if(planes.length <= index) {
|
|
PlaneNode@ node;
|
|
updatePlane(node, pos, size, color, mat);
|
|
planes.insertLast(node);
|
|
index += 1;
|
|
return node;
|
|
}
|
|
else {
|
|
updatePlane(planes[index], pos, size, color, mat);
|
|
index += 1;
|
|
return planes[index-1];
|
|
}
|
|
}
|
|
|
|
void truncatePlanes(array<PlaneNode@>& planes, uint index) {
|
|
uint cnt = planes.length;
|
|
for(uint i = index; i < cnt; ++i)
|
|
hidePlane(planes[i]);
|
|
planes.length = index;
|
|
}
|
|
|
|
void setWeaponArc(PlaneNode@ node, Object@ obj, const Effector@ efftr) {
|
|
if(efftr !is null && efftr.fireArc < pi) {
|
|
vec3d offset = obj.node_rotation * efftr.turretAngle;
|
|
double rad = vec2d(offset.x, offset.z).radians();
|
|
node.minRad = rad - efftr.fireArc;
|
|
node.maxRad = rad + efftr.fireArc;
|
|
}
|
|
else {
|
|
node.minRad = -pi;
|
|
node.maxRad = +pi;
|
|
}
|
|
}
|
|
|
|
void addWeapon(array<PlaneNode@>& planes, uint& index, Object@ obj, Hook@ hook, bool showWeapons = true) {
|
|
auto@ shows = cast<ShowsRange>(hook);
|
|
if(shows !is null) {
|
|
double range = 0;
|
|
Color color;
|
|
if(shows.getShowRange(obj, range, color)) {
|
|
auto@ node = addPlane(planes, index, obj.node_position, range, color);
|
|
setWeaponArc(node, obj, null);
|
|
}
|
|
return;
|
|
}
|
|
if(!showWeapons)
|
|
return;
|
|
AddTurret@ turr;
|
|
@turr = cast<AddTurret>(hook);
|
|
if(turr is null) {
|
|
auto@ ifh = cast<IfHook>(hook);
|
|
if(ifh !is null)
|
|
@hook = ifh.hook;
|
|
auto@ rep = cast<GenericRepeatHook>(hook);
|
|
if(rep !is null)
|
|
@turr = cast<AddTurret>(rep.hook);
|
|
}
|
|
if(turr is null)
|
|
return;
|
|
|
|
auto@ node = addPlane(planes, index, obj.node_position,
|
|
turr.range, turr.def.trailStart);
|
|
setWeaponArc(node, obj, null);
|
|
}
|
|
|
|
void update(Object@ obj) {
|
|
//Deal with not displaying any object
|
|
if(obj is null) {
|
|
hideAll();
|
|
return;
|
|
}
|
|
|
|
//Update movement beams
|
|
uint movInd = 0;
|
|
if(obj.owner.controlled) {
|
|
vec3d atPos = obj.node_position;
|
|
uint ordCnt = 0;
|
|
bool wasPath = false;
|
|
if(obj.hasLeaderAI && obj.hasOrders)
|
|
ordCnt = obj.orderCount;
|
|
if(obj.hasMover) {
|
|
if(obj.isMoving) {
|
|
if(obj.hasMovePath) {
|
|
DataList@ objs = obj.getMovePath();
|
|
Object@ current;
|
|
bool wasEven = obj.inFTL;
|
|
while(receive(objs, current)) {
|
|
if(current !is null) {
|
|
vec3d dest = strategicPosition(current);
|
|
addBeam(moveBeams, movInd,
|
|
atPos, dest,
|
|
wasEven ? FTL_BEAM_COLOR : MOVE_BEAM_COLOR);
|
|
atPos = dest;
|
|
wasPath = true;
|
|
wasEven = !wasEven;
|
|
}
|
|
}
|
|
}
|
|
if(wasPath || ordCnt == 0) {
|
|
vec3d dest = obj.moveDestination;
|
|
addBeam(moveBeams, movInd,
|
|
atPos, dest,
|
|
MOVE_BEAM_COLOR);
|
|
atPos = dest;
|
|
}
|
|
}
|
|
}
|
|
if(obj.hasLeaderAI) {
|
|
for(uint i = wasPath ? 1 : 0; i < ordCnt; ++i) {
|
|
if(obj.orderHasMovement[i]) {
|
|
vec3d dest = obj.orderMoveDestination[i];
|
|
uint type = obj.orderType[i];
|
|
bool isFTL = isFTLOrder(type);
|
|
|
|
addBeam(moveBeams, movInd,
|
|
atPos, dest,
|
|
isFTL ? FTL_BEAM_COLOR : MOVE_BEAM_COLOR);
|
|
atPos = dest;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
truncateBeams(moveBeams, movInd);
|
|
|
|
//Update height beam
|
|
Region@ region = obj.region;
|
|
vec3d myPos = strategicPosition(obj);
|
|
double myY = obj.position.y;
|
|
double regionY;
|
|
if(region !is null)
|
|
regionY = region.position.y;
|
|
else
|
|
regionY = myY;
|
|
uint rangeInd = 0;
|
|
bool showWeapons = (altKey || SHOW_FIREARCS) && (!isHover || !obj.selected);
|
|
|
|
bool hasHeight = obj.region !is null && abs(myY - regionY) >= 1.0;
|
|
if(hasHeight) {
|
|
vec3d from = obj.node_position;
|
|
vec3d to = from;
|
|
to.y = regionY;
|
|
|
|
updateBeam(heightBeam, from, to,
|
|
HEIGHT_BEAM_COLOR);
|
|
}
|
|
else {
|
|
hideBeam(heightBeam);
|
|
}
|
|
|
|
//Update link beam
|
|
Object@ link;
|
|
if(obj.isAsteroid) {
|
|
Asteroid@ roid = cast<Asteroid>(obj);
|
|
@link = roid.origin;
|
|
}
|
|
else if(obj.isOddity) {
|
|
@link = cast<Oddity>(obj).getLink();
|
|
}
|
|
|
|
if(link !is null && (link.visible || link.known)) {
|
|
vec3d linkPos = strategicPosition(link);
|
|
updateBeam(linkBeam, myPos, linkPos,
|
|
LINK_BEAM_COLOR);
|
|
}
|
|
else {
|
|
hideBeam(linkBeam);
|
|
}
|
|
|
|
//Update project beam
|
|
Object@ project;
|
|
if(project !is null)
|
|
updateBeam(projectBeam, myPos, project.position, PROJECT_BEAM_COLOR);
|
|
else
|
|
hideBeam(projectBeam);
|
|
|
|
if(obj.hasConstruction && obj.owner.controlled && obj.isRallying)
|
|
updateBeam(rallyBeam, myPos, obj.rallyPosition, RALLY_BEAM_COLOR);
|
|
else
|
|
hideBeam(rallyBeam);
|
|
|
|
if(obj.isStar) {
|
|
//Update trade link beams
|
|
uint tradeInd = 0;
|
|
Region@ reg = obj.region;
|
|
if(reg !is null) {
|
|
SystemDesc@ origin = getSystem(reg);
|
|
Empire@ originPrimary = origin.object.visiblePrimaryEmpire;
|
|
for(uint i = 0, cnt = origin.adjacent.length; i < cnt; ++i) {
|
|
SystemDesc@ other = getSystem(origin.adjacent[i]);
|
|
if(playerEmpire !is null && playerEmpire.valid && other.object.ExploredMask & playerEmpire.visionMask == 0)
|
|
continue;
|
|
|
|
Empire@ otherPrimary = other.object.visiblePrimaryEmpire;
|
|
|
|
Color col = TRADE_LINK_BEAM_COLOR;
|
|
if(originPrimary !is null && originPrimary is otherPrimary && otherPrimary.valid)
|
|
col = originPrimary.color;
|
|
col.a = 0x80;
|
|
|
|
vec3d offset = origin.position - other.position;
|
|
offset.y = 0.0;
|
|
offset.normalize();
|
|
|
|
addBeam(resourceBeams, tradeInd, origin.position - offset * origin.radius,
|
|
other.position + offset * other.radius, col);
|
|
}
|
|
}
|
|
truncateBeams(resourceBeams, tradeInd);
|
|
}
|
|
else {
|
|
//Update resource beams
|
|
uint resInd = 0;
|
|
if(obj.hasResources) {
|
|
array<Resource> resources;
|
|
resources.syncFrom(obj.getNativeResources());
|
|
|
|
uint nativeCnt = obj.nativeResourceCount;
|
|
|
|
for(uint i = 0; i < resources.length; ++i) {
|
|
auto@ res = resources[i];
|
|
if(res.origin !is obj)
|
|
continue;
|
|
|
|
Object@ dest = res.exportedTo;
|
|
if(dest is null)
|
|
@dest = obj.nativeResourceDestination[i];
|
|
if(dest is null || dest is obj)
|
|
continue;
|
|
if(dest is hovered || dest.selected)
|
|
continue;
|
|
|
|
vec3d theirPos = strategicPosition(dest);
|
|
|
|
Color color = EXPORT_BEAM_COLOR;
|
|
if(obj.owner !is playerEmpire || dest.owner !is playerEmpire)
|
|
color = QUEUED_EXPORT_BEAM_COLOR;
|
|
else if(!res.usable)
|
|
color = GlowBeamColor;
|
|
|
|
addBeam(resourceBeams, resInd,
|
|
myPos, theirPos, color);
|
|
}
|
|
|
|
if(obj.owner.controlled) {
|
|
resources.syncFrom(obj.getAvailableResources());
|
|
for(uint i = 0; i < resources.length; ++i) {
|
|
auto@ res = resources[i];
|
|
Object@ origin = res.origin;
|
|
if(origin is null || origin is obj)
|
|
continue;
|
|
|
|
vec3d theirPos = strategicPosition(origin);
|
|
|
|
Color color = IMPORT_BEAM_COLOR;
|
|
if(!res.usable)
|
|
color = GlowBeamColor;
|
|
|
|
addBeam(resourceBeams, resInd,
|
|
theirPos, myPos,
|
|
color);
|
|
|
|
if(showWeapons) {
|
|
auto@ type = res.type;
|
|
if(type !is null) {
|
|
for(uint j = 0, jcnt = type.hooks.length; j < jcnt; ++j)
|
|
addWeapon(ranges, rangeInd, obj, cast<Hook>(type.hooks[j]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(obj.queuedImportCount > 0) {
|
|
resources.syncFrom(obj.getQueuedImports());
|
|
for(uint i = 0; i < resources.length; ++i) {
|
|
auto@ res = resources[i];
|
|
Object@ origin = res.origin;
|
|
if(origin is null || origin is obj)
|
|
continue;
|
|
|
|
vec3d theirPos = strategicPosition(origin);
|
|
|
|
addBeam(resourceBeams, resInd,
|
|
theirPos, myPos,
|
|
QUEUED_IMPORT_BEAM_COLOR);
|
|
}
|
|
}
|
|
}
|
|
truncateBeams(resourceBeams, resInd);
|
|
}
|
|
|
|
//Weapon ranges
|
|
if(!isHover || !obj.selected) {
|
|
if(showWeapons && obj.isShip) {
|
|
Ship@ ship = cast<Ship>(obj);
|
|
auto@ bp = ship.blueprint;
|
|
if(bp is null)
|
|
return;
|
|
auto@ dsg = bp.design;
|
|
if(dsg is null)
|
|
return;
|
|
uint resInd = 0;
|
|
for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) {
|
|
auto@ ss = dsg.subsystems[i];
|
|
if(ss.has(SV_Range)) {
|
|
auto@ node = addPlane(ranges, rangeInd, obj.node_position,
|
|
ss[SV_Range], ss.type.typeColor);
|
|
|
|
if(ss.effectorCount > 0) {
|
|
auto@ efftr = ss.effectors[0];
|
|
setWeaponArc(node, obj, ss.effectors[0]);
|
|
}
|
|
else {
|
|
setWeaponArc(node, obj, null);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
if(obj.isOrbital) {
|
|
auto@ type = getOrbitalModule("FlingCore");
|
|
if(type !is null && cast<Orbital>(obj).coreModule == type.id) {
|
|
addPlane(ranges, rangeInd, obj.node_position,
|
|
FLING_BEACON_RANGE, Color(0x2bff0cff));
|
|
}
|
|
if(cacheObj !is obj || sectionCache is null || sectCacheTime < frameTime - 3.0) {
|
|
if(sectionCache is null)
|
|
@sectionCache = array<OrbitalSection>();
|
|
sectionCache.syncFrom(cast<Orbital>(obj).getSections());
|
|
sectCacheTime = frameTime;
|
|
@cacheObj = obj;
|
|
}
|
|
for(uint i = 0, cnt = sectionCache.length; i < cnt; ++i) {
|
|
auto@ sect = sectionCache[i];
|
|
for(uint j = 0, jcnt = sect.type.hooks.length; j < jcnt; ++j)
|
|
addWeapon(ranges, rangeInd, obj, cast<Hook>(sect.type.hooks[j]), showWeapons);
|
|
}
|
|
}
|
|
if(obj.isShip) {
|
|
Ship@ ship = cast<Ship>(obj);
|
|
auto@ bp = ship.blueprint;
|
|
if(bp !is null) {
|
|
auto@ dsg = bp.design;
|
|
if(dsg !is null) {
|
|
if(dsg.hasTag(ST_Slipstream)) {
|
|
addPlane(ranges, rangeInd, obj.node_position,
|
|
dsg.average(SV_SlipstreamOptimalDistance), Color(0x67a7ad));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(showWeapons && obj.isPlanet && obj.getBuildingCount() > 0) {
|
|
if(cacheObj !is obj || bldCache is null || bldCacheTime < frameTime - 2.9) {
|
|
if(bldCache is null)
|
|
@bldCache = array<const BuildingType@>();
|
|
bldCacheTime = frameTime;
|
|
@cacheObj = obj;
|
|
uint cnt = obj.getBuildingCount();
|
|
bldCache.length = 0;
|
|
bldCache.reserve(cnt);
|
|
for(uint i = 0, cnt = obj.getBuildingCount(); i < cnt; ++i) {
|
|
auto@ type = getBuildingType(obj.buildingType[i]);
|
|
if(type !is null)
|
|
bldCache.insertLast(type);
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = bldCache.length; i < cnt; ++i) {
|
|
auto@ type = bldCache[i];
|
|
for(uint j = 0, jcnt = type.hooks.length; j < jcnt; ++j)
|
|
addWeapon(ranges, rangeInd, obj, cast<Hook>(type.hooks[j]), showWeapons);
|
|
}
|
|
}
|
|
}
|
|
truncatePlanes(ranges, rangeInd);
|
|
}
|
|
};
|
|
|
|
array<BEAMS@> selectedBeams;
|
|
BEAMS hoverBeams;
|
|
|
|
void updateHoverBeams() {
|
|
hoverBeams.update(hovered);
|
|
}
|
|
|
|
void updateBeams() {
|
|
uint oldCnt = selectedBeams.length;
|
|
uint cnt = selection.length;
|
|
|
|
for(uint i = cnt; i < oldCnt; ++i)
|
|
selectedBeams[i].remove();
|
|
|
|
selectedBeams.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(selectedBeams[i] is null)
|
|
@selectedBeams[i] = BEAMS();
|
|
selectedBeams[i].primary = i == 0;
|
|
selectedBeams[i].update(selection[i]);
|
|
}
|
|
}
|
|
|
|
Color GlowBeamColor;
|
|
void tick(double time) {
|
|
float pct = abs((frameTime % 1.0) - 0.5f) * 2.f;
|
|
GlowBeamColor = DISABLED_BEAM_COLOR.interpolate(DISABLED_BEAM_COLOR2, pct);
|
|
|
|
updateBeams();
|
|
updateHoverBeams();
|
|
for(int i = selection.length - 1; i >= 0; --i) {
|
|
if(!selection[i].valid)
|
|
selection.removeAt(i);
|
|
}
|
|
}
|
|
|
|
void render(double time) {
|
|
if(debugCursor) {
|
|
vec3d mpos;
|
|
if(getElevationIntersect(activeCamera.screenToRay(mousePos), mpos)) {
|
|
double size = activeCamera.distance / 80.0;
|
|
renderBillboard(material::PlanetImage, mpos, size);
|
|
mpos.y = getElevation(mpos.x, mpos.z);
|
|
renderBillboard(material::SmallLogo, mpos, size);
|
|
}
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
for(uint i = 0; i < 9; ++i) {
|
|
uint cnt = groups[i].length;
|
|
file << cnt;
|
|
for(uint j = 0; j < cnt; ++j)
|
|
file << groups[i][j];
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
for(uint i = 0; i < 9; ++i) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
groups[i].length = cnt;
|
|
for(uint j = 0; j < cnt; ++j)
|
|
file >> groups[i][j];
|
|
}
|
|
}
|