Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+169
View File
@@ -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);
}
+121
View File
@@ -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);
}
+164
View File
@@ -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");
}
+440
View File
@@ -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;
}
+299
View File
@@ -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));
}
+312
View File
@@ -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));
}
+123
View File
@@ -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);
}
+223
View File
@@ -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);
}