Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import resources;
|
||||
import ftl;
|
||||
from obj_selection import selectedObject, selectedObjects, getSelectionPosition, getSelectionScale;
|
||||
import targeting.PointTarget;
|
||||
import targeting.targeting;
|
||||
from targeting.MoveTarget import getFleetTargetPositions;
|
||||
|
||||
class FlingTarget : PointTarget {
|
||||
double cost = 0.0;
|
||||
int scale = 1;
|
||||
Object@ obj;
|
||||
Object@ beacon;
|
||||
array<Object@> objs;
|
||||
bool inRange = false;
|
||||
|
||||
FlingTarget(Object@ beacon, Object@ obj, int totalScale, bool InRange) {
|
||||
@this.obj = obj;
|
||||
@this.beacon = beacon;
|
||||
scale = totalScale;
|
||||
inRange = InRange;
|
||||
objs = selectedObjects;
|
||||
}
|
||||
|
||||
vec3d get_origin() override {
|
||||
if(shiftKey) {
|
||||
Object@ obj = selectedObject;
|
||||
return obj.finalMoveDestination;
|
||||
}
|
||||
else {
|
||||
return getSelectionPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mpos) override {
|
||||
PointTarget::hover(mpos);
|
||||
if(selectedObjects.length > 1) {
|
||||
auto@ positions = getFleetTargetPositions(objs, hovered, isFTL=true);
|
||||
cost = 0;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i)
|
||||
cost += flingCost(objs[i], positions[i]);
|
||||
range = cost > playerEmpire.FTLStored ? 0.0 : INFINITY;
|
||||
}
|
||||
else {
|
||||
range = flingRange(obj);
|
||||
cost = flingCost(obj, hovered);
|
||||
}
|
||||
if(shiftKey)
|
||||
return canFlingTo(obj, hovered);
|
||||
if(beacon is null || !inRange)
|
||||
return false;
|
||||
return canFlingTo(obj, hovered) && distance <= range;
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
return shiftKey || (beacon !is null && inRange && distance <= range);
|
||||
}
|
||||
};
|
||||
|
||||
class FlingDisplay : PointDisplay {
|
||||
PlaneNode@ range;
|
||||
|
||||
~FlingDisplay() {
|
||||
if(range !is null) {
|
||||
range.visible = false;
|
||||
range.markForDeletion();
|
||||
@range = null;
|
||||
}
|
||||
}
|
||||
|
||||
void draw(TargetMode@ mode) override {
|
||||
PointDisplay::draw(mode);
|
||||
|
||||
FlingTarget@ ht = cast<FlingTarget>(mode);
|
||||
if(ht is null)
|
||||
return;
|
||||
|
||||
if(range is null && ht.beacon !is null) {
|
||||
@range = PlaneNode(material::RangeCircle, FLING_BEACON_RANGE);
|
||||
range.visible = false;
|
||||
range.position = ht.beacon.node_position;
|
||||
range.rebuildTransform();
|
||||
range.color = Color(0x2bff0cff);
|
||||
range.visible = true;
|
||||
}
|
||||
|
||||
Color color;
|
||||
if(ht.distance <= ht.range && ht.inRange && ht.valid)
|
||||
color = Color(0x00ff00ff);
|
||||
else
|
||||
color = Color(0xff0000ff);
|
||||
|
||||
font::DroidSans_11_Bold.draw(mousePos + vec2i(16, 0),
|
||||
toString(int(ht.cost)) + " " + locale::FTL
|
||||
+ " (" + toString(ht.distance, 0) + "u)",
|
||||
color);
|
||||
|
||||
if(ht.beacon is null) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::NEED_FLING_BEACON,
|
||||
color);
|
||||
}
|
||||
else if(!ht.inRange) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::OUT_OF_BEACON_RANGE,
|
||||
color);
|
||||
}
|
||||
else if(ht.distance > ht.range) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::INSUFFICIENT_FTL,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
inColor = Color(0x00c0ffff);
|
||||
if(shiftKey)
|
||||
outColor = Color(0xffe400ff);
|
||||
else
|
||||
outColor = colors::Red;
|
||||
FlingTarget@ ht = cast<FlingTarget>(mode);
|
||||
if(ht !is null && range !is null && ht.beacon !is null) {
|
||||
range.position = ht.beacon.node_position;
|
||||
range.rebuildTransform();
|
||||
}
|
||||
PointDisplay::render(mode);
|
||||
}
|
||||
};
|
||||
|
||||
class FlingCB : TargetCallback {
|
||||
Object@ beacon;
|
||||
|
||||
FlingCB(Object@ beacon) {
|
||||
@this.beacon = beacon;
|
||||
}
|
||||
|
||||
void call(TargetMode@ mode) override {
|
||||
if(beacon is null)
|
||||
return;
|
||||
|
||||
bool anyFlung = false;
|
||||
Object@[] selection = selectedObjects;
|
||||
auto@ positions = getFleetTargetPositions(selection, mode.position, isFTL=true);
|
||||
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
||||
Object@ obj = selection[i];
|
||||
if(!obj.hasMover || !obj.hasLeaderAI || !canFling(obj))
|
||||
continue;
|
||||
obj.addFlingOrder(beacon, positions[i], shiftKey || obj.inFTL);
|
||||
anyFlung = true;
|
||||
}
|
||||
|
||||
if(anyFlung)
|
||||
sound::order_fling.play(priority=true);
|
||||
}
|
||||
};
|
||||
|
||||
void targetFling() {
|
||||
Object@ sel = selectedObject;
|
||||
if(sel.owner is null || !sel.owner.valid)
|
||||
return;
|
||||
|
||||
Object@ beacon = sel.owner.getClosestFlingBeacon(sel);
|
||||
|
||||
FlingTarget targ(beacon, selectedObject, max(getSelectionScale(), 1),
|
||||
beacon !is null && beacon.position.distanceToSQ(sel.position) <= FLING_BEACON_RANGE_SQ);
|
||||
FlingDisplay disp;
|
||||
FlingCB cb(beacon);
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import resources;
|
||||
import ftl;
|
||||
from obj_selection import selectedObject, selectedObjects, getSelectionPosition, getSelectionScale;
|
||||
import targeting.PointTarget;
|
||||
import targeting.targeting;
|
||||
from targeting.MoveTarget import getFleetTargetPositions;
|
||||
|
||||
class HyperdriveTarget : PointTarget {
|
||||
double cost = 0.0;
|
||||
Object@ obj;
|
||||
array<vec3d>@ offsets;
|
||||
array<Object@> objs;
|
||||
|
||||
HyperdriveTarget(Object@ Obj) {
|
||||
@obj = Obj;
|
||||
objs = selectedObjects;
|
||||
}
|
||||
|
||||
vec3d get_origin() override {
|
||||
if(shiftKey) {
|
||||
Object@ obj = selectedObject;
|
||||
if(obj is null)
|
||||
return vec3d();
|
||||
return obj.finalMoveDestination;
|
||||
}
|
||||
else {
|
||||
return getSelectionPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mpos) override {
|
||||
PointTarget::hover(mpos);
|
||||
|
||||
//if(selectedObjects.length > 1) {
|
||||
auto@ positions = getFleetTargetPositions(objs, hovered);
|
||||
cost = 0;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i)
|
||||
cost += hyperdriveCost(objs[i], positions[i]);
|
||||
range = cost > playerEmpire.FTLStored ? 0.0 : INFINITY;
|
||||
//}
|
||||
//else {
|
||||
// range = hyperdriveRange(obj);
|
||||
// cost = hyperdriveCost(obj, hovered);
|
||||
//}
|
||||
return canHyperdriveTo(obj, hovered) && (distance <= range || shiftKey);
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
return distance <= range || shiftKey;
|
||||
}
|
||||
};
|
||||
|
||||
class HyperdriveDisplay : PointDisplay {
|
||||
void draw(TargetMode@ mode) override {
|
||||
PointDisplay::draw(mode);
|
||||
|
||||
HyperdriveTarget@ ht = cast<HyperdriveTarget>(mode);
|
||||
if(ht is null)
|
||||
return;
|
||||
|
||||
Color color;
|
||||
if(ht.distance <= ht.range && ht.valid)
|
||||
color = Color(0x00ff00ff);
|
||||
else
|
||||
color = Color(0xff0000ff);
|
||||
|
||||
font::DroidSans_11_Bold.draw(mousePos + vec2i(16, 0),
|
||||
toString(int(ht.cost)) + " " + locale::FTL
|
||||
+ " (" + toString(ht.distance, 0) + "u)",
|
||||
color);
|
||||
|
||||
if(ht.distance > ht.range) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::INSUFFICIENT_FTL,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
inColor = Color(0x00c0ffff);
|
||||
if(shiftKey)
|
||||
outColor = Color(0xffe400ff);
|
||||
else
|
||||
outColor = colors::Red;
|
||||
PointDisplay::render(mode);
|
||||
}
|
||||
};
|
||||
|
||||
class HyperdriveCB : TargetCallback {
|
||||
void call(TargetMode@ mode) override {
|
||||
bool anyDidFTL = false;
|
||||
Object@[] selection = selectedObjects;
|
||||
auto@ positions = getFleetTargetPositions(selection, mode.position);
|
||||
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
||||
Object@ obj = selection[i];
|
||||
if(!obj.hasMover || !obj.hasLeaderAI || !canHyperdrive(obj))
|
||||
continue;
|
||||
anyDidFTL = true;
|
||||
obj.addHyperdriveOrder(positions[i], shiftKey || obj.inFTL);
|
||||
}
|
||||
|
||||
if(anyDidFTL)
|
||||
sound::order_hyperdrive.play(priority=true);
|
||||
|
||||
if(shiftKey) {
|
||||
HyperdriveTarget targ(selectedObject);
|
||||
targ.isShifted = true;
|
||||
HyperdriveDisplay disp;
|
||||
HyperdriveCB cb;
|
||||
startTargeting(targ, disp, cb);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void targetHyperdrive() {
|
||||
HyperdriveTarget targ(selectedObject);
|
||||
HyperdriveDisplay disp;
|
||||
HyperdriveCB cb;
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import resources;
|
||||
import ftl;
|
||||
from obj_selection import selectedObject, selectedObjects, getSelectionPosition, getSelectionScale;
|
||||
import targeting.PointTarget;
|
||||
import targeting.targeting;
|
||||
from targeting.MoveTarget import getFleetTargetPositions;
|
||||
import system_flags;
|
||||
|
||||
class JumpdriveTarget : PointTarget {
|
||||
double cost = 0.0;
|
||||
Object@ obj;
|
||||
array<Object@> objs;
|
||||
|
||||
JumpdriveTarget(Object@ obj) {
|
||||
@this.obj = obj;
|
||||
objs = selectedObjects;
|
||||
range = INFINITY;
|
||||
}
|
||||
|
||||
vec3d get_origin() override {
|
||||
if(shiftKey) {
|
||||
Object@ obj = selectedObject;
|
||||
return obj.finalMoveDestination;
|
||||
}
|
||||
else {
|
||||
return getSelectionPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mpos) override {
|
||||
if(selectedObjects.length > 1) {
|
||||
auto@ positions = getFleetTargetPositions(objs, hovered, isFTL=true);
|
||||
cost = 0;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i)
|
||||
cost += jumpdriveCost(objs[i], positions[i]);
|
||||
}
|
||||
else {
|
||||
cost = jumpdriveCost(obj, hovered);
|
||||
}
|
||||
if(cost <= playerEmpire.FTLStored)
|
||||
range = INFINITY;
|
||||
else
|
||||
range = 0;
|
||||
PointTarget::hover(mpos);
|
||||
return canFlingTo(obj, hovered);
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class JumpdriveDisplay : PointDisplay {
|
||||
PlaneNode@ range;
|
||||
double jumpRange;
|
||||
|
||||
~JumpdriveDisplay() {
|
||||
if(range !is null) {
|
||||
range.visible = false;
|
||||
range.markForDeletion();
|
||||
@range = null;
|
||||
}
|
||||
}
|
||||
|
||||
void draw(TargetMode@ mode) override {
|
||||
PointDisplay::draw(mode);
|
||||
|
||||
JumpdriveTarget@ ht = cast<JumpdriveTarget>(mode);
|
||||
if(ht is null)
|
||||
return;
|
||||
|
||||
if(range is null) {
|
||||
jumpRange = cast<Ship>(ht.obj).blueprint.getEfficiencySum(SV_JumpRange);
|
||||
@range = PlaneNode(material::RangeCircle, jumpRange);
|
||||
range.visible = false;
|
||||
range.position = ht.obj.node_position;
|
||||
range.rebuildTransform();
|
||||
range.color = Color(0xff2b0cff);
|
||||
range.visible = true;
|
||||
}
|
||||
|
||||
bool isSafe = false;
|
||||
Region@ reg = getRegion(ht.hovered);
|
||||
if(reg !is null)
|
||||
isSafe = reg.getSystemFlag(playerEmpire, safetyFlag);
|
||||
|
||||
Color color;
|
||||
if(!ht.valid || ht.cost > playerEmpire.FTLStored)
|
||||
color = Color(0xff0000ff);
|
||||
else if(ht.distance >= jumpRange && !isSafe)
|
||||
color = Color(0xff8000ff);
|
||||
else
|
||||
color = Color(0x00ff00ff);
|
||||
|
||||
font::DroidSans_11_Bold.draw(mousePos + vec2i(16, 0),
|
||||
toString(int(ht.cost)) + " " + locale::FTL
|
||||
+ " (" + toString(ht.distance, 0) + "u)",
|
||||
color);
|
||||
|
||||
if(ht.cost > playerEmpire.FTLStored) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::INSUFFICIENT_FTL,
|
||||
color);
|
||||
}
|
||||
else if(ht.distance >= jumpRange && !isSafe) {
|
||||
if(ht.distance >= jumpRange * 2.0) {
|
||||
font::DroidSans_11_Bold.draw(mousePos + vec2i(16, 16),
|
||||
locale::JUMPDRIVE_SAFETY_WARNING_SEVERE,
|
||||
Color(0xff0000ff));
|
||||
}
|
||||
else {
|
||||
font::DroidSans_11.draw(mousePos + vec2i(16, 16),
|
||||
locale::JUMPDRIVE_SAFETY_WARNING,
|
||||
Color(0xff0000ff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
JumpdriveTarget@ ht = cast<JumpdriveTarget>(mode);
|
||||
if(ht !is null && range !is null) {
|
||||
range.position = ht.obj.node_position;
|
||||
range.rebuildTransform();
|
||||
}
|
||||
PointDisplay::render(mode);
|
||||
}
|
||||
};
|
||||
|
||||
class JumpdriveCB : TargetCallback {
|
||||
void call(TargetMode@ mode) override {
|
||||
bool anyFTL = false;
|
||||
Object@[] selection = selectedObjects;
|
||||
auto@ positions = getFleetTargetPositions(selection, mode.position, isFTL=true);
|
||||
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
||||
Object@ obj = selection[i];
|
||||
if(!obj.hasMover || !obj.hasLeaderAI || !canJumpdrive(obj))
|
||||
continue;
|
||||
obj.addJumpdriveOrder(positions[i], shiftKey || obj.inFTL);
|
||||
anyFTL = true;
|
||||
}
|
||||
|
||||
if(anyFTL)
|
||||
sound::order_fling.play(priority=true);
|
||||
}
|
||||
};
|
||||
|
||||
void targetJumpdrive() {
|
||||
Object@ sel = selectedObject;
|
||||
if(sel.owner is null || !sel.owner.valid)
|
||||
return;
|
||||
if(!selectedObject.isShip)
|
||||
return;
|
||||
|
||||
JumpdriveTarget targ(selectedObject);
|
||||
JumpdriveDisplay disp;
|
||||
JumpdriveCB cb;
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
}
|
||||
|
||||
int safetyFlag = -1;
|
||||
void init() {
|
||||
safetyFlag = getSystemFlag("JumpdriveSafety");
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
import targeting.targeting;
|
||||
from obj_selection import selectedObjects, getSelectionPosition, selectedObject;
|
||||
from navigation.elevation import getElevationIntersect;
|
||||
import targeting.PointTarget;
|
||||
import movement;
|
||||
import ftl;
|
||||
|
||||
enum MoveMode {
|
||||
MM_Normal,
|
||||
MM_Facing,
|
||||
MM_Height,
|
||||
};
|
||||
|
||||
const double VECTOR_DELAY = 0.2;
|
||||
const double VECTOR_PROGRESS_DELAY = 0.1;
|
||||
const int VECTOR_RADIAL_SIZE = 24;
|
||||
const int FACING_LENGTH = 200;
|
||||
const Color VECTOR_RADIAL_COLOR(0x39a086aa);
|
||||
|
||||
class MoveTarget : PointTarget {
|
||||
MoveMode mode = MM_Normal;
|
||||
vec3d destination;
|
||||
bool hasFacing = false;
|
||||
bool hasHeight = false;
|
||||
double origHeight = 0;
|
||||
vec3d facingDestination;
|
||||
vec2i startAt = mousePos;
|
||||
vec2i dragPos = startAt;
|
||||
double startTime = frameTime;
|
||||
|
||||
vec3d get_origin() {
|
||||
if(shiftKey) {
|
||||
Object@ obj = selectedObject;
|
||||
return obj.finalMoveDestination;
|
||||
}
|
||||
else {
|
||||
return getSelectionPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
quaterniond get_facing() {
|
||||
return quaterniond_fromVecToVec(vec3d_front(), facingDestination - destination);
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mpos) override {
|
||||
if(mode == MM_Facing || mode == MM_Height)
|
||||
return true;
|
||||
PointTarget::hover(mpos);
|
||||
destination = hovered;
|
||||
|
||||
//Keep height if in the same region
|
||||
if(!ctrlKey) {
|
||||
vec3d orig = origin;
|
||||
Region@ targRegion = getRegion(destination);
|
||||
Region@ curRegion = getRegion(orig);
|
||||
|
||||
if(curRegion !is null && targRegion is curRegion) {
|
||||
double hdiff = orig.y - curRegion.position.y;
|
||||
|
||||
line3dd ray = activeCamera.screenToRay(mpos);
|
||||
ray.start.y -= hdiff;
|
||||
ray.end.y -= hdiff;
|
||||
|
||||
if(getElevationIntersect(ray, destination))
|
||||
destination.y += hdiff;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onMouseButton(int button, bool pressed) override {
|
||||
if(!pressed) {
|
||||
if(button == 0) {
|
||||
mode = MM_Height;
|
||||
if(!hasHeight)
|
||||
origHeight = destination.y;
|
||||
return true;
|
||||
}
|
||||
else if(button == 1) {
|
||||
targetingClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(button == 0) {
|
||||
mode = MM_Facing;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseDragged(int buttons, int x, int y, int dx, int dy) override {
|
||||
if(buttons == 0x2) {
|
||||
dragPos.x += dx;
|
||||
dragPos.y -= dy;
|
||||
|
||||
if(mode == MM_Normal) {
|
||||
double curTime = frameTime;
|
||||
if(curTime - startTime > VECTOR_DELAY) {
|
||||
mode = MM_Height;
|
||||
if(!hasHeight)
|
||||
origHeight = destination.y;
|
||||
}
|
||||
else if(dragPos.distanceTo(startAt) < 4)
|
||||
return true;
|
||||
}
|
||||
if(mode == MM_Height) {
|
||||
double delta = double(dy) * 0.5;
|
||||
destination.y += delta;
|
||||
hasHeight = true;
|
||||
if(hasFacing)
|
||||
facingDestination.y += delta;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(buttons == 0x3) {
|
||||
dragPos.x += dx;
|
||||
dragPos.y -= dy;
|
||||
|
||||
if(mode == MM_Facing) {
|
||||
if(dragPos.distanceTo(startAt) > FACING_LENGTH)
|
||||
dragPos = startAt + (dragPos - startAt).normalize(FACING_LENGTH);
|
||||
PointTarget::hover(dragPos);
|
||||
facingDestination = hovered;
|
||||
facingDestination.y = destination.y;
|
||||
hasFacing = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cancelTargeting();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseDragEnd(int buttons) override {
|
||||
if(mode == MM_Height) {
|
||||
targetingClick();
|
||||
return true;
|
||||
}
|
||||
else if(mode == MM_Facing) {
|
||||
mode = MM_Height;
|
||||
return true;
|
||||
}
|
||||
cancelTargeting();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
if(playerEmpire.ForbidDeepSpace != 0) {
|
||||
if(getRegion(destination) is null) {
|
||||
cancelTargeting();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
vec3d get_position() override {
|
||||
return destination;
|
||||
}
|
||||
};
|
||||
|
||||
class MoveVisuals : TargetVisuals {
|
||||
BeamNode@ moveBeam;
|
||||
BeamNode@ facingBeam;
|
||||
BeamNode@ heightBeam;
|
||||
BeamNode@ lengthBeam;
|
||||
BeamNode@ destHeightBeam;
|
||||
|
||||
MoveVisuals() {
|
||||
@moveBeam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
||||
moveBeam.color = Color(0x00ff00ff);
|
||||
moveBeam.visible = false;
|
||||
|
||||
@facingBeam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
||||
facingBeam.color = Color(0x00adffff);
|
||||
facingBeam.visible = false;
|
||||
|
||||
@heightBeam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
||||
heightBeam.color = Color(0xaaaaaaff);
|
||||
heightBeam.visible = false;
|
||||
|
||||
@destHeightBeam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
||||
destHeightBeam.color = Color(0xaaaaaaff);
|
||||
destHeightBeam.visible = false;
|
||||
|
||||
@lengthBeam = BeamNode(material::MoveBeam, 0.001f, vec3d(), vec3d(), true);
|
||||
lengthBeam.color = Color(0xaaaaaaff);
|
||||
lengthBeam.visible = false;
|
||||
}
|
||||
|
||||
~MoveVisuals() {
|
||||
moveBeam.markForDeletion();
|
||||
facingBeam.markForDeletion();
|
||||
heightBeam.markForDeletion();
|
||||
destHeightBeam.markForDeletion();
|
||||
lengthBeam.markForDeletion();
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
MoveTarget@ mt = cast<MoveTarget>(mode);
|
||||
if(mt is null)
|
||||
return;
|
||||
|
||||
vec3d dest = mt.destination;
|
||||
vec3d origin = mt.origin;
|
||||
if(mt.hasHeight) {
|
||||
double origHeight = mt.origHeight;
|
||||
vec3d flatDest = dest;
|
||||
flatDest.y = origHeight;
|
||||
|
||||
vec3d flatOrigin = origin;
|
||||
flatOrigin.y = origHeight;
|
||||
|
||||
heightBeam.visible = true;
|
||||
heightBeam.abs_position = flatDest;
|
||||
heightBeam.endPosition = dest;
|
||||
|
||||
lengthBeam.visible = true;
|
||||
lengthBeam.abs_position = flatOrigin;
|
||||
lengthBeam.endPosition = flatDest;
|
||||
}
|
||||
if(mt.mode != MM_Normal || frameTime - mt.startTime > VECTOR_DELAY) {
|
||||
moveBeam.visible = true;
|
||||
moveBeam.abs_position = origin;
|
||||
moveBeam.endPosition = dest;
|
||||
}
|
||||
if(mt.hasFacing) {
|
||||
vec3d facing = mt.facingDestination;
|
||||
facingBeam.visible = true;
|
||||
facingBeam.abs_position = dest;
|
||||
facingBeam.endPosition = facing;
|
||||
|
||||
if(mt.hasHeight) {
|
||||
vec3d flatFacing = facing;
|
||||
flatFacing.y = mt.origHeight;
|
||||
|
||||
destHeightBeam.visible = true;
|
||||
destHeightBeam.abs_position = facing;
|
||||
destHeightBeam.endPosition = flatFacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(TargetMode@ mode) override {
|
||||
MoveTarget@ mt = cast<MoveTarget>(mode);
|
||||
if(mt is null)
|
||||
return;
|
||||
|
||||
//Draw facing progress radial
|
||||
if(mt.mode == MM_Normal) {
|
||||
double curTime = frameTime;
|
||||
double time = curTime - mt.startTime;
|
||||
|
||||
if(time > VECTOR_PROGRESS_DELAY && time < VECTOR_DELAY) {
|
||||
shader::PROGRESS = float(
|
||||
min(time - VECTOR_PROGRESS_DELAY,
|
||||
VECTOR_DELAY - VECTOR_PROGRESS_DELAY))
|
||||
/ float(VECTOR_DELAY - VECTOR_PROGRESS_DELAY);
|
||||
vec2i pos = mousePos;
|
||||
material::RadialProgress.draw(recti_area(
|
||||
pos - vec2i(VECTOR_RADIAL_SIZE / 2, VECTOR_RADIAL_SIZE / 2),
|
||||
vec2i(VECTOR_RADIAL_SIZE, VECTOR_RADIAL_SIZE)),
|
||||
VECTOR_RADIAL_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MoveCallback : TargetCallback {
|
||||
void call(TargetMode@ mode) override {
|
||||
MoveTarget@ mt = cast<MoveTarget>(mode);
|
||||
if(mt is null)
|
||||
return;
|
||||
|
||||
Object@[]@ selection = selectedObjects;
|
||||
if(selection.length == 0)
|
||||
return;
|
||||
|
||||
if(selection[0].hasSupportAI) {
|
||||
//Movement targeting for supports
|
||||
Object@[] supports;
|
||||
double maxRad = 0.0;
|
||||
Empire@ owner;
|
||||
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
||||
Object@ obj = selection[i];
|
||||
if(obj.owner !is null)
|
||||
@owner = obj.owner;
|
||||
if(obj.hasSupportAI && obj.owner.controlled) {
|
||||
supports.insertLast(obj);
|
||||
if(obj.radius > maxRad)
|
||||
maxRad = obj.radius;
|
||||
}
|
||||
}
|
||||
|
||||
if(supports.length == 0)
|
||||
return;
|
||||
|
||||
//Find fleet to transfer to
|
||||
Object@ fleet = cast<Ship>(selection[0]).Leader;
|
||||
if(fleet is null || mode.position.distanceTo(fleet.position) > fleet.getFormationRadius()) {
|
||||
if(owner !is null)
|
||||
@fleet = owner.getFleetFromPosition(mode.position);
|
||||
}
|
||||
if(fleet is null)
|
||||
return;
|
||||
|
||||
//Calculate formation
|
||||
double formRad = fleet.getFormationRadius();
|
||||
uint edge = ceil(sqrt(double(supports.length)));
|
||||
double spacing = max(maxRad, 1.0) * 8;
|
||||
vec3d boxSize(edge * spacing, 0, edge * spacing);
|
||||
double boxRad = sqrt(boxSize.x * boxSize.x / 4.0 + boxSize.z * boxSize.z / 4.0);
|
||||
vec3d boxCenter = (mode.position - fleet.position);
|
||||
|
||||
//Reverse entire fleet rotation
|
||||
Ship@ leadership = cast<Ship>(fleet);
|
||||
if(leadership !is null)
|
||||
boxCenter = leadership.formationDest.inverted() * boxCenter;
|
||||
|
||||
//Keep entire box within bounds
|
||||
if(boxCenter.length + boxRad > formRad)
|
||||
boxCenter.length = formRad - boxRad;
|
||||
|
||||
//Get the spacing vectors
|
||||
vec3d right = vec3d_right();
|
||||
vec3d front = vec3d_front();
|
||||
if(mt.hasFacing) {
|
||||
quaterniond facing = mt.facing;
|
||||
right = facing * right;
|
||||
front = facing * front;
|
||||
}
|
||||
if(leadership !is null) {
|
||||
right = leadership.formationDest.inverted() * right;
|
||||
front = leadership.formationDest.inverted() * front;
|
||||
}
|
||||
|
||||
right.length = spacing;
|
||||
front.length = spacing;
|
||||
|
||||
//Transfer ships to fleet
|
||||
for(uint i = 0, cnt = supports.length; i < cnt; ++i) {
|
||||
uint x = (i % edge);
|
||||
uint y = i / edge;
|
||||
|
||||
//Calculate position in formation
|
||||
vec3d pos = boxCenter;
|
||||
if(x % 2 == 0)
|
||||
pos += right * double(x / 2);
|
||||
else
|
||||
pos -= right * double(x / 2 + 1);
|
||||
if(y % 2 == 0)
|
||||
pos += front * double(y / 2);
|
||||
else
|
||||
pos -= front * double(y / 2 + 1);
|
||||
|
||||
//Transfer and form up
|
||||
Ship@ ship = cast<Ship>(supports[i]);
|
||||
if(ship.Leader !is fleet)
|
||||
supports[i].transferTo(fleet, pos);
|
||||
else
|
||||
supports[i].setFleetOffset(pos);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Movement order targeting
|
||||
if(selection.length == 1) {
|
||||
Object@ from = selection[0];
|
||||
|
||||
if(from.owner.controlled && from.hasMover && canMoveIndependently(from)) {
|
||||
sound::order_move.play(priority=true);
|
||||
if(mt.hasFacing)
|
||||
orderMove(from, mode.position, mt.facing, shiftKey);
|
||||
else
|
||||
orderMove(from, mode.position, shiftKey);
|
||||
}
|
||||
}
|
||||
else if(selection.length > 0) {
|
||||
array<Object@> orderObjs;
|
||||
//Get center to organize from
|
||||
vec3d center;
|
||||
double spacing = 1;
|
||||
for(int i = selection.length - 1; i >= 0; --i) {
|
||||
Object@ obj = selection[i];
|
||||
if(canMoveIndependently(obj)) {
|
||||
orderObjs.insertLast(obj);
|
||||
center += obj.position;
|
||||
|
||||
double size = obj.radius;
|
||||
if(obj.hasLeaderAI)
|
||||
size = obj.getFormationRadius();
|
||||
if(size > spacing)
|
||||
spacing = size;
|
||||
}
|
||||
}
|
||||
|
||||
if(orderObjs.length == 1) {
|
||||
sound::order_move.play(priority=true);
|
||||
if(mt.hasFacing)
|
||||
orderMove(orderObjs[0], mode.position, mt.facing, shiftKey);
|
||||
else
|
||||
orderMove(orderObjs[0], mode.position, shiftKey);
|
||||
}
|
||||
else {
|
||||
center /= double(orderObjs.length);
|
||||
spacing *= 2.5;
|
||||
|
||||
//Get center of target organization
|
||||
vec3d dest = mode.position;
|
||||
sound::order_move.play(priority=true);
|
||||
|
||||
//Form the objects up
|
||||
auto@ positions = getFleetTargetPositions(orderObjs, dest, mt.facing, !mt.hasFacing);
|
||||
for(uint i = 0, cnt = orderObjs.length; i < cnt; ++i) {
|
||||
if(mt.hasFacing)
|
||||
orderMove(orderObjs[i], positions[i], mt.facing, shiftKey);
|
||||
else
|
||||
orderMove(orderObjs[i], positions[i], shiftKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void targetMovement() {
|
||||
MoveTarget targ;
|
||||
MoveCallback cb;
|
||||
MoveVisuals vis;
|
||||
|
||||
startTargeting(targ, vis, cb);
|
||||
}
|
||||
|
||||
bool isExtendedMoveTarget() {
|
||||
MoveTarget@ targ = cast<MoveTarget>(mode);
|
||||
if(targ is null)
|
||||
return false;
|
||||
return frameTime - targ.startTime > VECTOR_DELAY;
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
import tabs.Tab;
|
||||
import tabs.tabbar;
|
||||
import abilities;
|
||||
import hooks;
|
||||
from gui import isGuiHovered;
|
||||
|
||||
import targeting.targeting;
|
||||
from obj_selection import hoveredObject, uiObject, selectedObjects;
|
||||
|
||||
export ObjectTargeting;
|
||||
export AbilityTargetObject;
|
||||
export targetObject;
|
||||
export toggleAbilityTargetObject;
|
||||
|
||||
import Tab@ createGalaxyTab() from "tabs.GalaxyTab";
|
||||
|
||||
class ObjectTargeting {
|
||||
Tab@ returnTo;
|
||||
Sprite icon(spritesheet::ContextIcons, 1);
|
||||
bool allowMultiple = false;
|
||||
bool isTemporary = false;
|
||||
bool drawCrosshair = true;
|
||||
Color validIconColor = colors::Green;
|
||||
Color errorIconColor = colors::Red;
|
||||
vec2i iconSize(40, 40);
|
||||
|
||||
bool valid(Object@ target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) {
|
||||
return target.name;
|
||||
}
|
||||
|
||||
string desc(Object@ target, bool valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
string emptyMessage() {
|
||||
return "";
|
||||
}
|
||||
|
||||
void hover(Object@ target, const vec2i& mouse) {
|
||||
}
|
||||
|
||||
void draw(Object@ target, bool valid) {
|
||||
}
|
||||
|
||||
void clear() {
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
}
|
||||
};
|
||||
|
||||
class AbilityTargetObject : ObjectTargeting {
|
||||
Ability@ abl;
|
||||
Targets@ targs;
|
||||
|
||||
AbilityTargetObject(Ability@ abl) {
|
||||
@this.abl = abl;
|
||||
@targs = Targets(abl.type.targets);
|
||||
icon = abl.type.icon;
|
||||
}
|
||||
|
||||
bool valid(Object@ target) override {
|
||||
@targs.fill(0).obj = target;
|
||||
return abl.canActivate(targs);
|
||||
}
|
||||
|
||||
void call(Object@ target) override {
|
||||
@targs.fill(0).obj = target;
|
||||
double range = abl.getRange(targs);
|
||||
array<Object@> objects = selectedObjects;
|
||||
if(objects.find(abl.obj) == -1)
|
||||
objects.insertLast(abl.obj);
|
||||
bool playedSound = false;
|
||||
for(uint i = 0, cnt = objects.length; i < cnt; ++i) {
|
||||
Object@ obj = objects[i];
|
||||
if(obj is null || (!obj.isArtifact && !obj.owner.controlled) || !obj.hasAbilities)
|
||||
continue;
|
||||
int ablId = -1;
|
||||
if(obj is abl.obj)
|
||||
ablId = abl.id;
|
||||
else
|
||||
ablId = obj.findAbilityOfType(abl.type.id);
|
||||
if(ablId != -1) {
|
||||
if(obj !is null) {
|
||||
if(obj.hasLeaderAI && range != INFINITY)
|
||||
obj.addAbilityOrder(ablId, target, range, shiftKey);
|
||||
else
|
||||
obj.activateAbility(ablId, target);
|
||||
}
|
||||
else
|
||||
abl.emp.activateAbility(ablId, target);
|
||||
if(abl.type.activateSound !is null && !playedSound) {
|
||||
abl.type.activateSound.play(priority=true);
|
||||
playedSound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) override {
|
||||
return abl.type.name;
|
||||
}
|
||||
|
||||
string desc(Object@ target, bool valid) {
|
||||
if(!valid) {
|
||||
string err = abl.getTargetError(targs);
|
||||
if(err.length != 0)
|
||||
return err;
|
||||
}
|
||||
return abl.formatCosts(targs);
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectMode : TargetMode {
|
||||
Object@ obj;
|
||||
ObjectTargeting@ targ;
|
||||
|
||||
bool isValidTarget(Object@ obj) override {
|
||||
if(obj is null)
|
||||
return false;
|
||||
if(!targ.valid(obj))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mouse) override {
|
||||
@obj = hoveredObject;
|
||||
if(obj is null)
|
||||
@obj = uiObject;
|
||||
targ.hover(obj, mouse);
|
||||
return isValidTarget(obj);
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
if(obj is null) {
|
||||
if(targ.isTemporary)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if(!targ.valid(obj)) {
|
||||
if(targ.isTemporary) {
|
||||
@obj = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string get_message() override {
|
||||
if(obj is null)
|
||||
return targ.emptyMessage();
|
||||
return targ.message(obj, valid);
|
||||
}
|
||||
|
||||
string get_desc() override {
|
||||
if(obj is null)
|
||||
return "";
|
||||
return targ.desc(obj, valid);
|
||||
}
|
||||
|
||||
Object@ get_target() override {
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectDisplay : TargetVisuals {
|
||||
ObjectTargeting@ targ;
|
||||
|
||||
void draw(TargetMode@ mode) override {
|
||||
if(isGuiHovered() && uiObject is null)
|
||||
return;
|
||||
|
||||
Color color;
|
||||
if(mode.valid)
|
||||
color = Color(0x00ff00ff);
|
||||
else
|
||||
color = Color(0xff3333ff);
|
||||
if(mode.target is null)
|
||||
color = Color(0xaaaaaaff);
|
||||
|
||||
Color iconColor = color;
|
||||
if(mode.target !is null && targ !is null) {
|
||||
if(mode.valid)
|
||||
iconColor = targ.validIconColor;
|
||||
else
|
||||
iconColor = targ.errorIconColor;
|
||||
}
|
||||
|
||||
//Draw the cursor
|
||||
vec2i isize = vec2i(40, 40);
|
||||
Sprite icon = spritesheet::ContextIcons+1;
|
||||
|
||||
if(targ !is null) {
|
||||
icon = targ.icon;
|
||||
isize = targ.iconSize;
|
||||
}
|
||||
icon.draw(recti_area(mousePos-isize/2, isize), iconColor);
|
||||
if(targ is null || targ.drawCrosshair) {
|
||||
drawLine(mousePos-vec2i(isize.x, 0), mousePos+vec2i(isize.x,0), color, 2);
|
||||
drawLine(mousePos-vec2i(0, isize.y), mousePos+vec2i(0,isize.y), color, 2);
|
||||
}
|
||||
|
||||
if(targ !is null)
|
||||
targ.draw(mode.target, mode.valid);
|
||||
|
||||
//Draw the message
|
||||
const Font@ ft = font::DroidSans_11_Bold;
|
||||
string err = mode.desc;
|
||||
if(err.length != 0) {
|
||||
ft.draw(recti_area(mousePos-vec2i(200,isize.y+30), vec2i(400, 20)), mode.message, horizAlign=0.5, color=color, stroke=colors::Black);
|
||||
@ft = font::OpenSans_11_Italic;
|
||||
ft.draw(recti_area(mousePos-vec2i(200,isize.y+5), vec2i(400, 20)), err, horizAlign=0.5, color=color, stroke=colors::Black);
|
||||
}
|
||||
else {
|
||||
ft.draw(recti_area(mousePos-vec2i(200,isize.y+5), vec2i(400, 20)), mode.message, horizAlign=0.5, color=color, stroke=colors::Black);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectCB : TargetCallback {
|
||||
ObjectTargeting@ targ;
|
||||
|
||||
void call(TargetMode@ mode) override {
|
||||
if(targ.isTemporary && mode.target is null) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
if(!mode.isShifted || !shiftKey)
|
||||
clear();
|
||||
targ.call(mode.target);
|
||||
targ.clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
targ.clear();
|
||||
if(targ.returnTo !is null) {
|
||||
if(targ.returnTo is ActiveTab.previous)
|
||||
popTab(ActiveTab);
|
||||
else
|
||||
switchToTab(targ.returnTo);
|
||||
}
|
||||
}
|
||||
|
||||
void cancel(bool wasExplicit) {
|
||||
clear();
|
||||
if(wasExplicit)
|
||||
targ.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
void targetObject(ObjectTargeting@ target, Tab@ returnTo = null, bool gotoTab = true) {
|
||||
ObjectMode targ;
|
||||
ObjectDisplay disp;
|
||||
ObjectCB cb;
|
||||
|
||||
@target.returnTo = returnTo;
|
||||
@targ.targ = target;
|
||||
@disp.targ = target;
|
||||
@cb.targ = target;
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
if(target.allowMultiple)
|
||||
targ.autoMultiple = true;
|
||||
|
||||
if(gotoTab) {
|
||||
Tab@ tab = findTab(TC_Galaxy);
|
||||
if(tab !is null) {
|
||||
switchToTab(tab);
|
||||
}
|
||||
else {
|
||||
if(returnTo !is null)
|
||||
browseTab(ActiveTab, createGalaxyTab(), true);
|
||||
else
|
||||
switchToTab(newTab(createGalaxyTab()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void toggleAbilityTargetObject(Ability@ abl) {
|
||||
auto@ om = cast<ObjectMode>(mode);
|
||||
if(om !is null) {
|
||||
auto@ t = cast<AbilityTargetObject>(om.targ);
|
||||
if(t !is null) {
|
||||
if(t.abl.obj is abl.obj && t.abl.id == abl.id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetObject(AbilityTargetObject(abl));
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
import navigation.SmartCamera;
|
||||
from input import activeCamera, mouseToGrid;
|
||||
from gui import isGuiHovered;
|
||||
import abilities;
|
||||
import targeting.targeting;
|
||||
import tabs.Tab;
|
||||
import tabs.tabbar;
|
||||
import Tab@ createGalaxyTab() from "tabs.GalaxyTab";
|
||||
|
||||
class PointTargeting {
|
||||
Tab@ returnTo;
|
||||
Sprite icon(spritesheet::ContextIcons, 1);
|
||||
bool allowMultiple = false;
|
||||
|
||||
bool valid(const vec3d& pos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
string message(const vec3d& pos, bool valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
void call(const vec3d& pos) {
|
||||
}
|
||||
|
||||
string desc(const vec3d& point, bool valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
double radius(const vec3d& point) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Color get_color() {
|
||||
return Color(0x00000000);
|
||||
}
|
||||
};
|
||||
|
||||
class AbilityTargetPoint : PointTargeting {
|
||||
Ability@ abl;
|
||||
Targets@ targs;
|
||||
|
||||
AbilityTargetPoint(Ability@ abl) {
|
||||
@this.abl = abl;
|
||||
icon = abl.type.icon;
|
||||
@targs = Targets(abl.type.targets);
|
||||
}
|
||||
|
||||
bool valid(const vec3d& target) override {
|
||||
targs.fill(0).point = target;
|
||||
return abl.canActivate(targs);
|
||||
}
|
||||
|
||||
void call(const vec3d& target) override {
|
||||
double range = abl.getRange(targs);
|
||||
if(abl.obj !is null && abl.obj.hasLeaderAI && range != INFINITY)
|
||||
abl.obj.addAbilityOrder(abl.id, target, range, shiftKey);
|
||||
else if(abl.obj !is null)
|
||||
abl.obj.activateAbility(abl.id, target);
|
||||
else
|
||||
abl.emp.activateAbility(abl.id, target);
|
||||
if(abl.type.activateSound !is null)
|
||||
abl.type.activateSound.play(priority=true);
|
||||
}
|
||||
|
||||
string message(const vec3d& target, bool valid) override {
|
||||
return abl.type.name;
|
||||
}
|
||||
|
||||
string desc(const vec3d& point, bool valid) {
|
||||
if(!valid) {
|
||||
string err = abl.getTargetError(targs);
|
||||
if(err.length != 0)
|
||||
return err;
|
||||
}
|
||||
double cost = abl.getEnergyCost(targs);
|
||||
if(cost != 0 && cost != abl.type.energyCost)
|
||||
return format("$1 $2", toString(cost,0), locale::RESOURCE_ENERGY);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
class PointTargetMode : TargetMode {
|
||||
PointTargeting@ targ;
|
||||
vec3d hovered;
|
||||
|
||||
bool hover(const vec2i& mouse) override {
|
||||
hovered = mouseToGrid(mouse);
|
||||
if(!targ.valid(hovered))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
if(!targ.valid(hovered))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
string get_message() override {
|
||||
return targ.message(hovered, valid);
|
||||
}
|
||||
|
||||
string get_desc() override {
|
||||
return targ.desc(hovered, valid);
|
||||
}
|
||||
|
||||
double get_radius() {
|
||||
return targ.radius(hovered);
|
||||
}
|
||||
};
|
||||
|
||||
class PointTargetDisplay : TargetVisuals {
|
||||
PointTargeting@ targ;
|
||||
void draw(TargetMode@ mode) override {
|
||||
if(isGuiHovered())
|
||||
return;
|
||||
|
||||
Color color = targ.color;
|
||||
if(color.a == 0) {
|
||||
if(mode.valid)
|
||||
color = Color(0x00ff00ff);
|
||||
else
|
||||
color = Color(0xff0000ff);
|
||||
}
|
||||
|
||||
//Draw the cursor
|
||||
drawLine(mousePos-vec2i(40, 0), mousePos+vec2i(40,0), color, 2);
|
||||
drawLine(mousePos-vec2i(0, 40), mousePos+vec2i(0,40), color, 2);
|
||||
targ.icon.draw(recti_area(mousePos-vec2i(20,20), vec2i(40,40)), color);
|
||||
|
||||
//Draw the message
|
||||
const Font@ ft = font::DroidSans_11_Bold;
|
||||
string err = mode.desc;
|
||||
if(err.length != 0) {
|
||||
ft.draw(recti_area(mousePos-vec2i(200,85), vec2i(400, 20)), mode.message, horizAlign=0.5, color=color);
|
||||
@ft = font::OpenSans_11_Italic;
|
||||
ft.draw(recti_area(mousePos-vec2i(200,65), vec2i(400, 20)), err, horizAlign=0.5, color=color);
|
||||
}
|
||||
else {
|
||||
ft.draw(recti_area(mousePos-vec2i(200,65), vec2i(400, 20)), mode.message, horizAlign=0.5, color=color);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PointTargetCB : TargetCallback {
|
||||
PointTargeting@ targ;
|
||||
void call(TargetMode@ mode) override {
|
||||
if(targ.returnTo !is null) {
|
||||
if(targ.returnTo is ActiveTab.previous)
|
||||
popTab(ActiveTab);
|
||||
else
|
||||
switchToTab(targ.returnTo);
|
||||
}
|
||||
targ.call(cast<PointTargetMode>(mode).hovered);
|
||||
}
|
||||
};
|
||||
|
||||
void targetPoint(PointTargeting@ target, Tab@ returnTo = null, bool gotoTab = true) {
|
||||
PointTargetMode targ;
|
||||
PointTargetDisplay disp;
|
||||
PointTargetCB cb;
|
||||
|
||||
@target.returnTo = returnTo;
|
||||
@targ.targ = target;
|
||||
@disp.targ = target;
|
||||
@cb.targ = target;
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
if(target.allowMultiple)
|
||||
targ.autoMultiple = true;
|
||||
|
||||
if(gotoTab) {
|
||||
Tab@ tab = findTab(TC_Galaxy);
|
||||
if(tab !is null) {
|
||||
switchToTab(tab);
|
||||
}
|
||||
else {
|
||||
if(returnTo !is null)
|
||||
browseTab(ActiveTab, createGalaxyTab(), true);
|
||||
else
|
||||
switchToTab(newTab(createGalaxyTab()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PointTarget : TargetMode {
|
||||
Object@ originObj;
|
||||
vec3d originPos;
|
||||
vec3d hovered;
|
||||
double range = -1.0;
|
||||
|
||||
vec3d get_origin() {
|
||||
if(originObj !is null)
|
||||
return originObj.node_position;
|
||||
return originPos;
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mouse) override {
|
||||
hovered = mouseToGrid(mouse);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
vec3d get_position() override {
|
||||
return hovered;
|
||||
}
|
||||
|
||||
double get_distance() {
|
||||
return origin.distanceTo(position);
|
||||
}
|
||||
|
||||
double get_radius() {
|
||||
return 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
class PointDisplay : TargetVisuals {
|
||||
BeamNode@ node;
|
||||
BeamNode@ node2;
|
||||
PlaneNode@ plane;
|
||||
|
||||
Color inColor = colors::Green;
|
||||
Color outColor = colors::Red;
|
||||
Color radiusColor = Color(0x00c0ff80);
|
||||
|
||||
PointDisplay() {
|
||||
@node = BeamNode(material::MoveBeam, 0.002f, vec3d(), vec3d(), true);
|
||||
node.visible = false;
|
||||
|
||||
@node2 = BeamNode(material::MoveBeam, 0.002f, vec3d(), vec3d(), true);
|
||||
node2.visible = false;
|
||||
|
||||
@plane = PlaneNode(material::Circle, 0.0);
|
||||
plane.visible = false;
|
||||
plane.color = radiusColor;
|
||||
}
|
||||
|
||||
~PointDisplay() {
|
||||
node.visible = false;
|
||||
node.markForDeletion();
|
||||
node2.visible = false;
|
||||
node2.markForDeletion();
|
||||
plane.visible = false;
|
||||
plane.markForDeletion();
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
PointTarget@ pt = cast<PointTarget>(mode);
|
||||
if(pt is null)
|
||||
return;
|
||||
|
||||
if(pt.range >= 0.0 && pt.distance > pt.range) {
|
||||
vec3d origin = pt.origin;
|
||||
vec3d target = pt.position;
|
||||
|
||||
vec3d dir = (target - origin).normalize(pt.range);
|
||||
vec3d midpos = origin + dir;
|
||||
|
||||
//In range section
|
||||
node.position = origin;
|
||||
node.endPosition = midpos;
|
||||
node.rebuildTransform();
|
||||
node.color = inColor;
|
||||
node.visible = true;
|
||||
|
||||
//Out of range section
|
||||
node2.position = midpos;
|
||||
node2.endPosition = target;
|
||||
node2.rebuildTransform();
|
||||
node2.color = outColor;
|
||||
node2.visible = true;
|
||||
}
|
||||
else {
|
||||
node.position = pt.origin;
|
||||
node.endPosition = pt.position;
|
||||
node.rebuildTransform();
|
||||
node.color = inColor;
|
||||
|
||||
node.visible = true;
|
||||
node2.visible = false;
|
||||
}
|
||||
|
||||
double rad = pt.radius;
|
||||
if(rad != 0) {
|
||||
plane.position = pt.position;
|
||||
plane.scale = rad;
|
||||
plane.rebuildTransform();
|
||||
plane.visible = true;
|
||||
}
|
||||
else {
|
||||
plane.visible = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void toggleAbilityTargetPoint(Ability@ abl) {
|
||||
auto@ om = cast<PointTargetMode>(mode);
|
||||
if(om !is null) {
|
||||
auto@ t = cast<AbilityTargetPoint>(om.targ);
|
||||
if(t !is null) {
|
||||
if(t.abl.obj is abl.obj && t.abl.id == abl.id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetPoint(AbilityTargetPoint(abl));
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import resources;
|
||||
import ftl;
|
||||
from obj_selection import selectedObject, selectedObjects, getSelectionPosition, getSelectionScale;
|
||||
import targeting.PointTarget;
|
||||
import targeting.targeting;
|
||||
|
||||
class SlipstreamTarget : PointTarget {
|
||||
double cost = 0.0;
|
||||
int scale = 1;
|
||||
Object@ obj;
|
||||
|
||||
SlipstreamTarget(Object@ obj, int totalScale) {
|
||||
@this.obj = obj;
|
||||
scale = totalScale;
|
||||
}
|
||||
|
||||
vec3d get_origin() override {
|
||||
if(shiftKey)
|
||||
return obj.finalMoveDestination;
|
||||
else
|
||||
return obj.position;
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mpos) override {
|
||||
PointTarget::hover(mpos);
|
||||
cost = slipstreamCost(obj, scale, distance);
|
||||
range = slipstreamRange(obj, scale, playerEmpire.FTLStored);
|
||||
return canSlipstreamTo(obj, hovered) && (distance <= range || shiftKey);
|
||||
}
|
||||
|
||||
double get_radius() override {
|
||||
return slipstreamInaccuracy(obj, hovered);
|
||||
}
|
||||
|
||||
bool click() override {
|
||||
return distance <= range || shiftKey;
|
||||
}
|
||||
};
|
||||
|
||||
class SlipstreamDisplay : PointDisplay {
|
||||
void draw(TargetMode@ mode) override {
|
||||
PointDisplay::draw(mode);
|
||||
|
||||
SlipstreamTarget@ ht = cast<SlipstreamTarget>(mode);
|
||||
if(ht is null)
|
||||
return;
|
||||
|
||||
Color color;
|
||||
if(ht.distance <= ht.range && ht.valid)
|
||||
color = Color(0x00ff00ff);
|
||||
else
|
||||
color = Color(0xff0000ff);
|
||||
|
||||
font::DroidSans_11_Bold.draw(mousePos + vec2i(16, 0),
|
||||
toString(int(ht.cost)) + " " + locale::FTL
|
||||
+ " (" + toString(ht.distance, 0) + "u)",
|
||||
color);
|
||||
|
||||
if(ht.distance > ht.range) {
|
||||
font::OpenSans_11_Italic.draw(mousePos + vec2i(16, 16),
|
||||
locale::INSUFFICIENT_FTL,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) override {
|
||||
inColor = Color(0x00c0ffff);
|
||||
if(shiftKey)
|
||||
outColor = Color(0xffe400ff);
|
||||
else
|
||||
outColor = colors::Red;
|
||||
PointDisplay::render(mode);
|
||||
}
|
||||
};
|
||||
|
||||
class SlipstreamCB : TargetCallback {
|
||||
void call(TargetMode@ mode) override {
|
||||
bool anyOpenedTear = false;
|
||||
Object@[]@ selection = selectedObjects;
|
||||
for(uint i = 0, cnt = selection.length; i < cnt; ++i) {
|
||||
Object@ obj = selection[i];
|
||||
if(!obj.hasMover || !obj.hasLeaderAI)
|
||||
continue;
|
||||
if(!canSlipstream(obj))
|
||||
continue;
|
||||
obj.addSlipstreamOrder(mode.position, shiftKey || obj.inFTL);
|
||||
anyOpenedTear = true;
|
||||
for(uint j = 0; j < cnt; ++j) {
|
||||
if(i == j)
|
||||
continue;
|
||||
Object@ other = selection[j];
|
||||
if(!obj.hasMover || !obj.hasLeaderAI)
|
||||
continue;
|
||||
other.addWaitOrder(obj, shiftKey || obj.inFTL, moveTo=true);
|
||||
obj.addSecondaryToSlipstream(other);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(anyOpenedTear)
|
||||
sound::order_slipstream.play(priority=true);
|
||||
}
|
||||
};
|
||||
|
||||
void targetSlipstream() {
|
||||
Object@ sel = selectedObject;
|
||||
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i) {
|
||||
if(canSlipstream(selectedObjects[i])) {
|
||||
@sel = selectedObjects[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sel.owner is null || !sel.owner.valid)
|
||||
return;
|
||||
if(!canSlipstream(sel))
|
||||
return;
|
||||
|
||||
SlipstreamTarget targ(sel, max(getSelectionScale(), 1));
|
||||
SlipstreamDisplay disp;
|
||||
SlipstreamCB cb;
|
||||
|
||||
startTargeting(targ, disp, cb);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
#priority render 50
|
||||
#priority draw -10
|
||||
|
||||
funcdef bool HoverFilterCB(Object@);
|
||||
HoverFilterCB@ hoverFilter, lastHoverFilter;
|
||||
|
||||
class TargetMode {
|
||||
bool valid = false;
|
||||
bool isShifted = false;
|
||||
bool autoMultiple = false;
|
||||
|
||||
bool isValidTarget(Object@ obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hover(const vec2i& mouse) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool click() {
|
||||
return false;
|
||||
}
|
||||
|
||||
vec3d get_position() {
|
||||
return vec3d();
|
||||
}
|
||||
|
||||
Object@ get_target() {
|
||||
return null;
|
||||
}
|
||||
|
||||
string get_message() {
|
||||
return "";
|
||||
}
|
||||
|
||||
string get_desc() {
|
||||
return "";
|
||||
}
|
||||
|
||||
bool onMouseWheel(int x, int y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseButton(int button, bool pressed) {
|
||||
if(!pressed) {
|
||||
if(button == 0) {
|
||||
targetingClick();
|
||||
return true;
|
||||
}
|
||||
else if(button == 1) {
|
||||
cancelTargeting(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(button == 0 || button == 1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseMoved(int x, int y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseDragged(int buttons, int x, int y, int dx, int dy) {
|
||||
if(buttons == 0x1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseDragEnd(int buttons) {
|
||||
if(buttons == 0x1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onKeyEvent(int key, bool pressed) {
|
||||
if(key == KEY_ESC) {
|
||||
if(pressed) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
cancelTargeting();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class TargetVisuals {
|
||||
void draw(TargetMode@ mode) {
|
||||
}
|
||||
|
||||
void render(TargetMode@ mode) {
|
||||
}
|
||||
};
|
||||
|
||||
class TargetCallback {
|
||||
void call(TargetMode@ mode) {
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
}
|
||||
|
||||
void cancel(bool wasExplicit) {
|
||||
cancel();
|
||||
}
|
||||
};
|
||||
|
||||
TargetMode@ mode;
|
||||
TargetVisuals@ visuals;
|
||||
TargetCallback@ cb;
|
||||
bool shown = false;
|
||||
|
||||
bool isTargeting() {
|
||||
return shown && mode !is null;
|
||||
}
|
||||
|
||||
bool targetingClick() {
|
||||
if(mode is null)
|
||||
return false;
|
||||
if(!mode.click()) {
|
||||
sound::error.play(priority=true);
|
||||
return true;
|
||||
}
|
||||
auto@ _callback = cb;
|
||||
auto@ _mode = mode;
|
||||
|
||||
if(!mode.autoMultiple || !shiftKey) {
|
||||
cancelTargeting();
|
||||
}
|
||||
else {
|
||||
sound::generic_click.play(priority=true);
|
||||
mode.isShifted = true;
|
||||
}
|
||||
|
||||
if(_callback !is null)
|
||||
_callback.call(_mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool targetMouseWheel(int x, int y) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onMouseWheel(x, y);
|
||||
}
|
||||
|
||||
bool targetMouseButton(int button, bool pressed) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onMouseButton(button, pressed);
|
||||
}
|
||||
|
||||
bool targetMouseMoved(int x, int y) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onMouseMoved(x, y);
|
||||
}
|
||||
|
||||
bool targetMouseDragged(int buttons, int x, int y, int dx, int dy) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onMouseDragged(buttons, x, y, dx, dy);
|
||||
}
|
||||
|
||||
bool targetMouseDragEnd(int buttons) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onMouseDragEnd(buttons);
|
||||
}
|
||||
|
||||
bool targetKeyEvent(int key, bool pressed) {
|
||||
if(!shown || mode is null)
|
||||
return false;
|
||||
return mode.onKeyEvent(key, pressed);
|
||||
}
|
||||
|
||||
void startTargeting(TargetMode@ Mode, TargetVisuals@ Visuals, TargetCallback@ Cb) {
|
||||
@mode = Mode;
|
||||
@visuals = Visuals;
|
||||
@cb = Cb;
|
||||
@hoverFilter = HoverFilterCB(mode.isValidTarget);
|
||||
}
|
||||
|
||||
void cancelTargeting(bool wasExplicit = false) {
|
||||
if(cb !is null)
|
||||
cb.cancel(wasExplicit);
|
||||
@mode = null;
|
||||
@visuals = null;
|
||||
@cb = null;
|
||||
@hoverFilter = null;
|
||||
}
|
||||
|
||||
void showTargeting() {
|
||||
shown = true;
|
||||
}
|
||||
|
||||
void hideTargeting() {
|
||||
shown = false;
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
if(shown && mode !is null) {
|
||||
bool valid = mode.hover(mousePos);
|
||||
if(mode !is null) {
|
||||
mode.valid = valid;
|
||||
if(mode.isShifted && !shiftKey)
|
||||
cancelTargeting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(shown && visuals !is null)
|
||||
visuals.draw(mode);
|
||||
}
|
||||
|
||||
void render(double time) {
|
||||
if(shown && visuals !is null)
|
||||
visuals.render(mode);
|
||||
}
|
||||
Reference in New Issue
Block a user