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 selection; array> 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@ 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(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& 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& 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& objs, SelectionType type) { if(type == ST_Other) return; array 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 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 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(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@ 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(obj).strategicIconPosition; if(obj.isOddity) return cast(obj).strategicIconPosition; if(obj.isOrbital) return cast(obj).strategicIconPosition; return obj.node_position; } class BEAMS { BeamNode@ heightBeam; BeamNode@ linkBeam; BeamNode@ projectBeam; BeamNode@ rallyBeam; array moveBeams; array resourceBeams; array ranges; bool primary = false; bool isHover = false; Object@ cacheObj; array@ sectionCache; double sectCacheTime = -INFINITY; array@ 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& 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& beams, uint index) { uint cnt = beams.length; for(uint i = index; i < cnt; ++i) hideBeam(beams[i]); beams.length = index; } PlaneNode@ addPlane(array& 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& 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& planes, uint& index, Object@ obj, Hook@ hook, bool showWeapons = true) { auto@ shows = cast(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(hook); if(turr is null) { auto@ ifh = cast(hook); if(ifh !is null) @hook = ifh.hook; auto@ rep = cast(hook); if(rep !is null) @turr = cast(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(obj); @link = roid.origin; } else if(obj.isOddity) { @link = cast(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 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(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(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(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(); sectionCache.syncFrom(cast(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(sect.type.hooks[j]), showWeapons); } } if(obj.isShip) { Ship@ ship = cast(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(); 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(type.hooks[j]), showWeapons); } } } truncatePlanes(ranges, rangeInd); } }; array 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]; } }