Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
//Adds functionality to the normal engine camera to make it
|
||||
//more intelligent and context-sensitive
|
||||
|
||||
import double getElevation(double x, double z) from "navigation.elevation";
|
||||
import double getAverageElevation() from "navigation.elevation";
|
||||
import bool getElevationIntersect(const line3dd& line, vec3d& point) from "navigation.elevation";
|
||||
import Object@ get_hoveredObject() from "obj_selection";
|
||||
import saving;
|
||||
|
||||
bool CAM_PANNED = false;
|
||||
bool CAM_ZOOMED = false;
|
||||
bool CAM_ROTATED = false;
|
||||
|
||||
double OBJLOCK_ZOOMDIST = 5.0;
|
||||
|
||||
class SmartCamera : Savable {
|
||||
//Main engine camera
|
||||
Camera camera;
|
||||
|
||||
//Configuration
|
||||
double camAnglesPerPixel;
|
||||
double movePerPixel;
|
||||
double tickZoom;
|
||||
|
||||
//Object locks
|
||||
Object@ lockedObject;
|
||||
vec3d lockOffset;
|
||||
|
||||
SmartCamera() {
|
||||
camAnglesPerPixel = 0.22 * twopi / 360.0;
|
||||
movePerPixel = 8.0;
|
||||
tickZoom = 1.2;
|
||||
camera.setPositionBound(vec3d(-200000), vec3d(200000));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
//Set the camera to the normal angle
|
||||
camera.resetRotation();
|
||||
camera.resetZoom();
|
||||
camera.zoom(5.0);
|
||||
camera.pitch(0.7);
|
||||
camera.snap();
|
||||
}
|
||||
|
||||
vec3d get_lookAt() {
|
||||
return camera.lookAt;
|
||||
}
|
||||
|
||||
vec3d get_finalLookAt() {
|
||||
return camera.finalLookAt;
|
||||
}
|
||||
|
||||
double screenAngle(const vec3d& pos) {
|
||||
return camera.screenAngle(pos);
|
||||
}
|
||||
|
||||
double get_distance() {
|
||||
return camera.distance;
|
||||
}
|
||||
|
||||
//Rotate by a certain amount of screen pixels dragged
|
||||
void rotate(int dx, int dy) {
|
||||
CAM_ROTATED = true;
|
||||
if(settings::bInvertHorizRot)
|
||||
dx = -dx;
|
||||
if(settings::bInvertVertRot)
|
||||
dy = -dy;
|
||||
|
||||
if(settings::bFreeCamera) {
|
||||
camera.yaw(clamp(double(dx) * camAnglesPerPixel, -pi * 0.5, pi * 0.5));
|
||||
camera.pitch(clamp(double(dy) * camAnglesPerPixel, -pi * 0.5, pi * 0.5));
|
||||
}
|
||||
else {
|
||||
camera.abs_yaw(clamp(double(dx) * camAnglesPerPixel, -pi * 0.5, pi * 0.5));
|
||||
camera.pitch(clamp(double(dy) * camAnglesPerPixel, -pi * 0.5, pi * 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
//Roll based on dragged script pixels
|
||||
void roll(int delta) {
|
||||
CAM_ROTATED = true;
|
||||
camera.roll(double(delta) * camAnglesPerPixel);
|
||||
}
|
||||
|
||||
//Zoom with world plane
|
||||
void zoom(int delta) {
|
||||
//Get zoom speed
|
||||
double factor;
|
||||
if(settings::bInvertZoom)
|
||||
factor = pow(tickZoom, delta * settings::dZoomSpeed);
|
||||
else
|
||||
factor = pow(tickZoom, -delta * settings::dZoomSpeed);
|
||||
CAM_ZOOMED = true;
|
||||
|
||||
if(lockedObject !is null) {
|
||||
camera.zoom(factor);
|
||||
return;
|
||||
}
|
||||
|
||||
if(factor < 1.0 ? settings::bZoomToCursor : settings::bZoomFromCursor) {
|
||||
if(hoveredObject !is null) {
|
||||
double minDist = hoveredObject.radius * getMinZoomDist(hoveredObject);
|
||||
vec3d point = hoveredObject.position;
|
||||
camera.zoomTo(factor, point, minDist);
|
||||
}
|
||||
else {
|
||||
//Interpolate plane position
|
||||
line3dd ray = screenToRay(mousePos);
|
||||
if(factor > 1.0)
|
||||
ray.start -= ray.direction * 1.0;
|
||||
|
||||
vec3d point;
|
||||
if(!getElevationIntersect(ray, point))
|
||||
return;
|
||||
|
||||
double minDist = 0.0;
|
||||
camera.zoomTo(factor, point, minDist);
|
||||
}
|
||||
}
|
||||
else {
|
||||
camera.zoom(factor);
|
||||
}
|
||||
}
|
||||
|
||||
void zoomTo(Object@ obj) {
|
||||
clearObjectLock();
|
||||
zoomTo(obj.position);
|
||||
}
|
||||
|
||||
void zoomTo(const vec3d& pos) {
|
||||
clearObjectLock();
|
||||
vec3d finalPos = camera.finalLookAt;
|
||||
vec3d movement;
|
||||
movement.x = pos.x - finalPos.x;
|
||||
movement.z = pos.z - finalPos.z;
|
||||
movement.y = getElevation(pos.x, pos.z) - finalPos.y;
|
||||
|
||||
camera.move_abs(movement);
|
||||
}
|
||||
|
||||
double getMinZoomDist(Object@ obj) {
|
||||
switch(obj.type) {
|
||||
case OT_Star:
|
||||
return 1.4;
|
||||
case OT_Planet:
|
||||
return 1.6;
|
||||
}
|
||||
return 1.15;
|
||||
}
|
||||
|
||||
//Pan on top of the world plane
|
||||
void pan(int dx, int dy) {
|
||||
if(!settings::bInvertPanX)
|
||||
dx = -dx;
|
||||
if(settings::bInvertPanY)
|
||||
dy = -dy;
|
||||
|
||||
//Normal world plane movement
|
||||
vec2d movement;
|
||||
movement.x = movePerPixel * double(dx);
|
||||
movement.y = movePerPixel * double(dy);
|
||||
|
||||
pan(movement);
|
||||
}
|
||||
|
||||
void pan(vec2d movement) {
|
||||
clearObjectLock();
|
||||
|
||||
//Speed based on zoom and modifiers
|
||||
double speed = camera.distance / 2500.0;
|
||||
if(settings::bModSpeedPan) {
|
||||
if(shiftKey)
|
||||
speed *= 3.0;
|
||||
else if(altKey)
|
||||
speed *= 0.333;
|
||||
}
|
||||
|
||||
movement *= settings::dPanSpeed * speed;
|
||||
|
||||
//Normal world plane movement
|
||||
vec3d prevPos = camera.finalLookAt;
|
||||
camera.move_world_abs(vec3d(movement.x, 0, movement.y));
|
||||
|
||||
//Change in elevation
|
||||
vec3d nextPos = camera.finalLookAt;
|
||||
camera.move_abs(vec3d(0, getElevation(nextPos.x, nextPos.z) - prevPos.y, 0));
|
||||
CAM_PANNED = true;
|
||||
}
|
||||
|
||||
bool panTo(const vec3d& pos, double speed, double threshold = 10.0) {
|
||||
clearObjectLock();
|
||||
|
||||
//Modify speed
|
||||
speed *= max(camera.distance / 2500.0, 0.2);
|
||||
if(settings::bModSpeedPan) {
|
||||
if(shiftKey)
|
||||
speed *= 3.0;
|
||||
else if(altKey)
|
||||
speed *= 0.333;
|
||||
}
|
||||
speed *= settings::dPanSpeed;
|
||||
|
||||
//Pan on a flat plane
|
||||
vec3d cur = lookAt;
|
||||
vec3d final = finalLookAt;
|
||||
|
||||
vec3d flatPos = pos;
|
||||
flatPos.y = 0;
|
||||
cur.y = 0;
|
||||
final.y = 0;
|
||||
|
||||
vec3d panDir = flatPos - final;
|
||||
double len = panDir.length;
|
||||
if(len <= threshold) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if(len > speed)
|
||||
panDir.length = speed;
|
||||
|
||||
vec3d prevPos = camera.finalLookAt;
|
||||
camera.move_abs(panDir);
|
||||
vec3d nextPos = camera.finalLookAt;
|
||||
camera.move_abs(vec3d(0, getElevation(nextPos.x, nextPos.z) - prevPos.y, 0));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
line3dd screenToRay(const vec2i& pos) {
|
||||
vec2i size = screenSize;
|
||||
return camera.screenToRay(double(pos.x) / double(size.x),
|
||||
double(pos.y) / double(size.y));
|
||||
}
|
||||
|
||||
vec3d screenToPoint(const vec2i& pos) {
|
||||
line3dd ray = screenToRay(mousePos);
|
||||
vec3d point;
|
||||
if(!getElevationIntersect(ray, point))
|
||||
ray.intersectY(point, getAverageElevation(), false);
|
||||
return point;
|
||||
}
|
||||
|
||||
void clearObjectLock() {
|
||||
@lockedObject = null;
|
||||
}
|
||||
|
||||
void zoomLockObject(Object& obj) {
|
||||
zoomTo(obj.position);
|
||||
camera.move_abs(obj.node_position - camera.finalLookAt);
|
||||
if(!shiftKey)
|
||||
camera.zoom((obj.radius * OBJLOCK_ZOOMDIST) / camera.distance);
|
||||
setLocked(obj);
|
||||
}
|
||||
|
||||
void setLocked(Object& obj) {
|
||||
@lockedObject = obj;
|
||||
lockOffset = camera.finalLookAt - obj.node_position;
|
||||
}
|
||||
|
||||
void animateLocked(double time) {
|
||||
if(lockedObject is null)
|
||||
return;
|
||||
if(!lockedObject.visible) {
|
||||
@lockedObject = null;
|
||||
return;
|
||||
}
|
||||
|
||||
vec3d finalPos = camera.finalLookAt;
|
||||
double tDiff = frameGameTime - lockedObject.lastTick;
|
||||
vec3d objPos = lockedObject.position + (lockedObject.velocity + lockedObject.acceleration * (tDiff * 0.5)) * tDiff;
|
||||
if(finalPos.distanceToSQ(objPos) > sqr(lockedObject.radius * 2.0 + lockedObject.velocity.length)) {
|
||||
@lockedObject = null;
|
||||
return;
|
||||
}
|
||||
|
||||
objPos += lockOffset;
|
||||
vec3d movement = objPos - finalPos;
|
||||
bool snap = camera.lookAt.distanceTo(finalPos) < 15.0;
|
||||
camera.move_abs(movement);
|
||||
if(snap)
|
||||
camera.snapTranslation();
|
||||
}
|
||||
|
||||
void animate(double time) {
|
||||
camera.animate(time);
|
||||
if(lockedObject !is null)
|
||||
animateLocked(time);
|
||||
}
|
||||
|
||||
void save(SaveFile& file) {
|
||||
file << camera.finalLookAt;
|
||||
file << camera.radius;
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
vec3d position;
|
||||
file >> position;
|
||||
if(file >= SV_0149) {
|
||||
double radius = 0;
|
||||
file >> radius;
|
||||
camera.radius = radius;
|
||||
}
|
||||
camera.move_abs(position - camera.finalLookAt);
|
||||
camera.snap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#priority init 50
|
||||
import systems;
|
||||
|
||||
ElevationMap emap;
|
||||
double epower = 4.0;
|
||||
double egrid = 1000.0;
|
||||
double vgrid = 1000.0;
|
||||
bool drawGrid = false;
|
||||
|
||||
void init() {
|
||||
recalculateElevation();
|
||||
|
||||
addConsoleCommand("grid_smoothing", GridPowerCommand());
|
||||
addConsoleCommand("grid_spacing", GridSpacingCommand());
|
||||
addConsoleCommand("grid_visual", GridVisualCommand());
|
||||
addConsoleCommand("show_grid", ShowGridCommand());
|
||||
}
|
||||
|
||||
void recalculateElevation() {
|
||||
//Elevation map
|
||||
emap.clear();
|
||||
vec2d minPoint, maxPoint;
|
||||
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
|
||||
auto@ sys = getSystem(i);
|
||||
vec3d pos = sys.position;
|
||||
emap.addPoint(pos, sys.radius);
|
||||
|
||||
minPoint.x = min(minPoint.x, pos.x);
|
||||
minPoint.y = min(minPoint.y, pos.y);
|
||||
maxPoint.x = max(maxPoint.x, pos.x);
|
||||
maxPoint.y = max(maxPoint.y, pos.y);
|
||||
}
|
||||
|
||||
emap.generate(vec2d(max(egrid, (maxPoint.x-minPoint.x)/100.0), max(egrid, (maxPoint.y-minPoint.y)/100.0)), epower);
|
||||
}
|
||||
|
||||
double getElevation(double x, double z) {
|
||||
return emap.get(x, z);
|
||||
}
|
||||
|
||||
double getAverageElevation() {
|
||||
return emap.gridStart.y;
|
||||
}
|
||||
|
||||
bool getElevationIntersect(const line3dd& line, vec3d&out point) {
|
||||
return emap.getClosestPoint(line, point);
|
||||
}
|
||||
|
||||
class GridPowerCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
epower = toDouble(args);
|
||||
emap.generate(vec2d(egrid, egrid), epower);
|
||||
}
|
||||
};
|
||||
|
||||
class GridSpacingCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
egrid = toDouble(args);
|
||||
emap.generate(vec2d(egrid, egrid), epower);
|
||||
}
|
||||
};
|
||||
|
||||
class GridVisualCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
vgrid = toDouble(args);
|
||||
}
|
||||
};
|
||||
|
||||
class ShowGridCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
drawGrid = args.length == 0 || toBool(args);
|
||||
}
|
||||
};
|
||||
|
||||
void render(double time) {
|
||||
if(!drawGrid)
|
||||
return;
|
||||
|
||||
//Draw the elevation grid
|
||||
for(int x = -10, xcnt = emap.gridSize.x / vgrid + 10; x < xcnt; ++x) {
|
||||
for(int y = -10, ycnt = emap.gridSize.y / vgrid + 10; y < ycnt; ++y) {
|
||||
vec3d tlpos = emap.gridStart + vec3d(vgrid * x, 0, vgrid * y);
|
||||
vec3d brpos = tlpos + vec3d(vgrid, 0.0, vgrid);
|
||||
|
||||
vec3d tl = vec3d(tlpos.x, emap.get(tlpos.x, tlpos.z), tlpos.z);
|
||||
vec3d tr = vec3d(brpos.x, emap.get(brpos.x, tlpos.z), tlpos.z);
|
||||
vec3d bl = vec3d(tlpos.x, emap.get(tlpos.x, brpos.z), brpos.z);
|
||||
vec3d br = vec3d(brpos.x, emap.get(brpos.x, brpos.z), brpos.z);
|
||||
|
||||
drawPolygonStart(2, material::TestGrid);
|
||||
drawPolygonPoint(tl, vec2f(0.f, 0.f));
|
||||
drawPolygonPoint(tr, vec2f(1.f, 0.f));
|
||||
drawPolygonPoint(br, vec2f(1.f, 1.f));
|
||||
|
||||
drawPolygonPoint(tl, vec2f(0.f, 0.f));
|
||||
drawPolygonPoint(bl, vec2f(0.f, 1.f));
|
||||
drawPolygonPoint(br, vec2f(1.f, 1.f));
|
||||
drawPolygonEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
from obj_selection import selectedObjects, selectedObject;
|
||||
import ftl;
|
||||
|
||||
import void targetHyperdrive() from "targeting.Hyperdrive";
|
||||
import void targetJumpdrive() from "targeting.Jumpdrive";
|
||||
import void targetFling() from "targeting.Fling";
|
||||
import void targetSlipstream() from "targeting.Slipstream";
|
||||
|
||||
bool canMove() {
|
||||
Object@[]@ selected = selectedObjects;
|
||||
if(selected.length == 0)
|
||||
return false;
|
||||
for(uint i = 0, cnt = selected.length; i < cnt; ++i) {
|
||||
if(!selected[i].hasMover)
|
||||
return false;
|
||||
if(!selected[i].owner.controlled)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canHyperdrive() {
|
||||
Object@[]@ selected = selectedObjects;
|
||||
if(selected.length == 0)
|
||||
return false;
|
||||
for(uint i = 0, cnt = selected.length; i < cnt; ++i) {
|
||||
if(!canHyperdrive(selected[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canJumpdrive() {
|
||||
Object@[]@ selected = selectedObjects;
|
||||
if(selected.length == 0)
|
||||
return false;
|
||||
for(uint i = 0, cnt = selected.length; i < cnt; ++i) {
|
||||
if(!canJumpdrive(selected[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canFling() {
|
||||
Object@[]@ selected = selectedObjects;
|
||||
if(selected.length == 0 || !playerEmpire.hasFlingBeacons)
|
||||
return false;
|
||||
for(uint i = 0, cnt = selected.length; i < cnt; ++i) {
|
||||
Object@ obj = selected[i];
|
||||
if(obj.owner is null || !obj.owner.valid)
|
||||
return false;
|
||||
if(!canFling(obj))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canSlipstream() {
|
||||
Object@[]@ selected = selectedObjects;
|
||||
for(uint i = 0, cnt = selected.length; i < cnt; ++i) {
|
||||
Object@ obj = selected[i];
|
||||
if(canSlipstream(obj))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool targetFTL() {
|
||||
if(!canMove())
|
||||
return false;
|
||||
|
||||
//Check for fling
|
||||
if(canFling()) {
|
||||
targetFling();
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check for hyperdrive
|
||||
if(canHyperdrive()) {
|
||||
targetHyperdrive();
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check for jumpdrive
|
||||
if(canJumpdrive()) {
|
||||
targetJumpdrive();
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check for slipstream
|
||||
if(canSlipstream()) {
|
||||
targetSlipstream();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FTLBind(bool pressed) {
|
||||
if(!pressed) {
|
||||
if(!targetFTL())
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
keybinds::Global.addBind(KB_FTL, "FTLBind");
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
#priority init -1
|
||||
import tabs.Tab;
|
||||
import elements.GuiTextbox;
|
||||
import systems;
|
||||
/*import research;*/
|
||||
import resources;
|
||||
from input import ActiveCamera;
|
||||
|
||||
import uint get_tabCount() from "tabs.tabbar";
|
||||
import Tab@ get_Tabs(uint i) from "tabs.tabbar";
|
||||
import Tab@ get_ActiveTab() from "tabs.tabbar";
|
||||
import void switchToTab(Tab@) from "tabs.tabbar";
|
||||
import Tab@ newTab(Tab@) from "tabs.tabbar";
|
||||
import void browseTab(Tab@,Tab@,bool) from "tabs.tabbar";
|
||||
import void browseTab(Tab@,bool) from "tabs.tabbar";
|
||||
import Tab@ findTab(int cat) from "tabs.tabbar";
|
||||
import Tab@ createResearchTab() from "tabs.ResearchTab";
|
||||
import Tab@ createDesignOverviewTab() from "tabs.DesignOverviewTab";
|
||||
import Tab@ createDesignEditorTab(const Design@) from "tabs.DesignEditorTab";
|
||||
import Tab@ createGalaxyTab() from "tabs.GalaxyTab";
|
||||
import Tab@ createGalaxyTab(Object@) from "tabs.GalaxyTab";
|
||||
import Tab@ createGalaxyTab(vec3d) from "tabs.GalaxyTab";
|
||||
/*import Tab@ createResearchTab(ResearchNode@ node) from "tabs.ResearchTab";*/
|
||||
/*import void researchTabView(Tab@ tab, ResearchNode@ node) from "tabs.ResearchTab";*/
|
||||
import void zoomTabTo(vec3d pos) from "tabs.GalaxyTab";
|
||||
import void zoomTo(Object@ obj) from "tabs.GalaxyTab";
|
||||
|
||||
const uint ITEM_COUNT = 5;
|
||||
|
||||
class GoItem {
|
||||
Sprite get_icon() {
|
||||
return Sprite();
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_NULL;
|
||||
}
|
||||
|
||||
int match(const string&) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return "(null)";
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return "";
|
||||
}
|
||||
|
||||
void go() {
|
||||
}
|
||||
|
||||
bool go(TabCategory cat) {
|
||||
Tab@ found = findTab(cat);
|
||||
if(shiftKey || found is null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
switchToTab(found);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void go(Tab@ tab, TabCategory cat) {
|
||||
if(shiftKey) {
|
||||
newTab(tab);
|
||||
switchToTab(tab);
|
||||
}
|
||||
else {
|
||||
Tab@ found = findTab(cat);
|
||||
if(ctrlKey || found is null) {
|
||||
browseTab(tab, false); //TODO: History
|
||||
}
|
||||
else {
|
||||
browseTab(found, tab, false); //TODO: History
|
||||
switchToTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void go(Tab@ tab) {
|
||||
if(shiftKey) {
|
||||
newTab(tab);
|
||||
switchToTab(tab);
|
||||
}
|
||||
else {
|
||||
browseTab(tab, false); //TODO: History
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
string str_galaxy = locale::GALAXY_CAMERA;
|
||||
class GoGalaxy : GoItem {
|
||||
int match(const string& str) {
|
||||
if(str_galaxy.startswith_nocase(str))
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_GalaxyIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return str_galaxy;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createGalaxyTab());
|
||||
}
|
||||
};
|
||||
|
||||
string str_research = locale::RESEARCH_OVERVIEW;
|
||||
class GoResearch : GoItem {
|
||||
int match(const string& str) {
|
||||
if(str_research.startswith_nocase(str))
|
||||
return 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_ResearchIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return str_research;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createResearchTab());
|
||||
}
|
||||
};
|
||||
|
||||
string str_designs = locale::DESIGN_OVERVIEW;
|
||||
class GoDesigns : GoItem {
|
||||
int match(const string& str) {
|
||||
if(str_designs.startswith_nocase(str))
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_DesignsIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return str_designs;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createDesignOverviewTab());
|
||||
}
|
||||
};
|
||||
|
||||
class GoConsole : GoItem {
|
||||
int match(const string& str) {
|
||||
if(locale::CONSOLE.startswith_nocase(str))
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return locale::CONSOLE;
|
||||
}
|
||||
|
||||
void go() {
|
||||
toggleConsole();
|
||||
}
|
||||
};
|
||||
|
||||
class GoSystem : GoItem {
|
||||
SystemDesc@ sys;
|
||||
|
||||
GoSystem(SystemDesc@ _sys) {
|
||||
@sys = _sys;
|
||||
}
|
||||
|
||||
int match(const string& str) {
|
||||
if(playerEmpire.valid && sys.object.ExploredMask & playerEmpire.mask == 0)
|
||||
return 0;
|
||||
if(sys.object.name.contains_nocase(str))
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_GalaxyIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return sys.object.name;
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return locale::SYSTEM;
|
||||
}
|
||||
|
||||
void go() {
|
||||
if(go(TC_Galaxy))
|
||||
zoomTabTo(sys.position);
|
||||
else
|
||||
go(createGalaxyTab(sys.position));
|
||||
}
|
||||
}
|
||||
|
||||
string str_tab = locale::TAB;
|
||||
class GoTab : GoItem {
|
||||
Tab@ tab;
|
||||
|
||||
GoTab(Tab@ _tab) {
|
||||
@tab = _tab;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return tab.icon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return tab.title;
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return str_tab;
|
||||
}
|
||||
|
||||
void go() {
|
||||
switchToTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
string str_design = locale::DESIGN;
|
||||
class GoDesign : GoItem {
|
||||
const Design@ dsg;
|
||||
|
||||
GoDesign(const Design@ _dsg) {
|
||||
@dsg = _dsg;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_DesignsIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return dsg.name;
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return str_design;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createDesignEditorTab(dsg), TC_Designs);
|
||||
}
|
||||
}
|
||||
|
||||
string str_planet = locale::PLANET;
|
||||
class GoPlanet : GoItem {
|
||||
Object@ obj;
|
||||
|
||||
GoPlanet(Object@ _obj) {
|
||||
@obj = _obj;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_GalaxyIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return obj.name;
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return str_planet;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createGalaxyTab(obj), TC_Galaxy);
|
||||
}
|
||||
}
|
||||
|
||||
string str_ship = locale::SHIP;
|
||||
class GoShip : GoItem {
|
||||
Object@ obj;
|
||||
|
||||
GoShip(Object@ _obj) {
|
||||
@obj = _obj;
|
||||
}
|
||||
|
||||
SkinStyle get_skinIcon() {
|
||||
return SS_GalaxyIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return obj.name;
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return str_ship;
|
||||
}
|
||||
|
||||
void go() {
|
||||
go(createGalaxyTab(obj), TC_Galaxy);
|
||||
}
|
||||
}
|
||||
|
||||
/*string str_technology = locale::TECHNOLOGY;*/
|
||||
/*class GoTechnology : GoItem {*/
|
||||
/* ResearchNode@ node;*/
|
||||
|
||||
/* GoTechnology(ResearchNode@ _node) {*/
|
||||
/* @node = _node;*/
|
||||
/* }*/
|
||||
|
||||
/* SkinStyle get_skinIcon() {*/
|
||||
/* return SS_ResearchIcon;*/
|
||||
/* }*/
|
||||
|
||||
/* string get_text() {*/
|
||||
/* return node.tech.name;*/
|
||||
/* }*/
|
||||
|
||||
/* string get_type() {*/
|
||||
/* return str_technology;*/
|
||||
/* }*/
|
||||
|
||||
/* void go() {*/
|
||||
/* go(createResearchTab(node), TC_Research);*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
class GoResource : GoItem {
|
||||
const ResourceType@ resType;
|
||||
|
||||
GoResource(const ResourceType@ type) {
|
||||
@this.resType = type;
|
||||
}
|
||||
|
||||
int match(const string& str) {
|
||||
if(resType.name.contains_nocase(str) && str.length >= 3) {
|
||||
if(str.equals_nocase(resType.name))
|
||||
return 10;
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return resType.smallIcon;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return format(locale::GO_RESOURCE, resType.name);
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return format(locale::GO_RESOURCE_TYPE, resType.name);
|
||||
}
|
||||
|
||||
void go() {
|
||||
uint sysCnt = systemCount;
|
||||
Planet@ best;
|
||||
double closestDist = INFINITY;
|
||||
bool foundOwned = false;
|
||||
vec3d camPos = ActiveCamera.lookAt;
|
||||
|
||||
for(uint i = 0; i < sysCnt; ++i) {
|
||||
auto@ sys = getSystem(i);
|
||||
uint plCnt = sys.object.planetCount;
|
||||
for(uint n = 0; n < plCnt; ++n) {
|
||||
Planet@ pl = sys.object.planets[n];
|
||||
if(pl.known) {
|
||||
auto@ type = getResource(pl.nativeResourceType[0]);
|
||||
if(type !is null && type is resType) {
|
||||
Empire@ owner = pl.visibleOwner;
|
||||
if(owner is playerEmpire) {
|
||||
Object@ dest = pl.nativeResourceDestination[0];
|
||||
if(dest !is null)
|
||||
continue;
|
||||
if(type.isMaterial(pl.level))
|
||||
continue;
|
||||
double dist = camPos.distanceToSQ(pl.position);
|
||||
if(dist < closestDist || !foundOwned) {
|
||||
@best = pl;
|
||||
closestDist = dist;
|
||||
foundOwned = true;
|
||||
}
|
||||
}
|
||||
else if(owner is null || !owner.valid) {
|
||||
if(!foundOwned) {
|
||||
double dist = camPos.distanceToSQ(pl.position);
|
||||
if(dist < closestDist) {
|
||||
@best = pl;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(best !is null)
|
||||
zoomTo(best);
|
||||
}
|
||||
};
|
||||
|
||||
class GoResourceClass : GoItem {
|
||||
const ResourceClass@ resType;
|
||||
|
||||
GoResourceClass(const ResourceClass@ type) {
|
||||
@this.resType = type;
|
||||
}
|
||||
|
||||
int match(const string& str) {
|
||||
if(resType.name.contains_nocase(str) && str.length >= 3) {
|
||||
if(str.equals_nocase(resType.name))
|
||||
return 9;
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
string get_text() {
|
||||
return format(locale::GO_RESOURCE, resType.name);
|
||||
}
|
||||
|
||||
string get_type() {
|
||||
return format(locale::GO_RESOURCE_TYPE, resType.name);
|
||||
}
|
||||
|
||||
void go() {
|
||||
uint sysCnt = systemCount;
|
||||
Planet@ best;
|
||||
double closestDist = INFINITY;
|
||||
bool foundOwned = false;
|
||||
vec3d camPos = ActiveCamera.lookAt;
|
||||
|
||||
for(uint i = 0; i < sysCnt; ++i) {
|
||||
auto@ sys = getSystem(i);
|
||||
uint plCnt = sys.object.planetCount;
|
||||
for(uint n = 0; n < plCnt; ++n) {
|
||||
Planet@ pl = sys.object.planets[n];
|
||||
if(pl.known) {
|
||||
auto@ type = getResource(pl.nativeResourceType[0]);
|
||||
if(type !is null && type.cls is resType) {
|
||||
Empire@ owner = pl.visibleOwner;
|
||||
if(owner is playerEmpire) {
|
||||
Object@ dest = pl.nativeResourceDestination[0];
|
||||
if(dest !is null)
|
||||
continue;
|
||||
if(type.isMaterial(pl.level))
|
||||
continue;
|
||||
double dist = camPos.distanceToSQ(pl.position);
|
||||
if(dist < closestDist || !foundOwned) {
|
||||
@best = pl;
|
||||
closestDist = dist;
|
||||
foundOwned = true;
|
||||
}
|
||||
}
|
||||
else if(owner is null || !owner.valid) {
|
||||
if(!foundOwned) {
|
||||
double dist = camPos.distanceToSQ(pl.position);
|
||||
if(dist < closestDist) {
|
||||
@best = pl;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(best !is null)
|
||||
zoomTo(best);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
GoItem@[] staticItems;
|
||||
|
||||
bool WouldInsert(uint amount, int match, int[]& priorities) {
|
||||
if(match < 0)
|
||||
return false;
|
||||
if(priorities[amount - 1] == -1)
|
||||
return true;
|
||||
if(priorities[amount - 1] >= match)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GoInsert(GoItem@ item, uint amount, int match, GoItem@[]& list, int[]& priorities) {
|
||||
if(list[amount - 1] !is null && priorities[amount - 1] >= match)
|
||||
return;
|
||||
|
||||
for(int j = amount - 1; j >= 0; --j) {
|
||||
//Push it up
|
||||
if(j < int(amount) - 1) {
|
||||
priorities[j + 1] = priorities[j];
|
||||
@list[j + 1] = list[j];
|
||||
}
|
||||
|
||||
//Check if we should insert here
|
||||
if(j == 0 || match < priorities[j - 1]) {
|
||||
//Insert here
|
||||
priorities[j] = match;
|
||||
@list[j] = item;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GoSearch(const string& text, GoItem@[]& list, uint amount = 5) {
|
||||
//Cache of priorities
|
||||
int[] priorities;
|
||||
priorities.length = amount;
|
||||
|
||||
//Clear the list
|
||||
list.length = amount;
|
||||
for(uint i = 0; i < amount; ++i) {
|
||||
@list[i] = null;
|
||||
priorities[i] = -1;
|
||||
}
|
||||
|
||||
//Empty text matches nothing
|
||||
if(text.length == 0)
|
||||
return;
|
||||
|
||||
//Search static items
|
||||
uint cnt = staticItems.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
int match = staticItems[i].match(text);
|
||||
if(match > 0)
|
||||
GoInsert(staticItems[i], amount, match, list, priorities);
|
||||
}
|
||||
|
||||
//Search tabs
|
||||
cnt = tabCount;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Tab@ tab = Tabs[i];
|
||||
if(tab.title.contains_nocase(text)) {
|
||||
if(WouldInsert(amount, 50, priorities))
|
||||
GoInsert(GoTab(tab), amount, 50, list, priorities);
|
||||
}
|
||||
}
|
||||
|
||||
//Search designs
|
||||
{
|
||||
ReadLock lock(playerEmpire.designMutex);
|
||||
cnt = playerEmpire.designCount;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
const Design@ dsg = playerEmpire.designs[i];
|
||||
if(dsg.name.contains_nocase(text)) {
|
||||
int p = text.length > 3 ? 10 : 2;
|
||||
if(dsg.name.equals_nocase(text))
|
||||
p += 100;
|
||||
if(WouldInsert(amount, p, priorities))
|
||||
GoInsert(GoDesign(dsg), amount, p, list, priorities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Search empire objects
|
||||
//cnt = playerEmpire.objectCount;
|
||||
//for(uint i = 0; i < cnt; ++i) {
|
||||
//Object@ obj = playerEmpire.objects[i];
|
||||
//Ship@ ship = cast<Ship@>(obj);
|
||||
//Planet@ pl = cast<Planet@>(obj);
|
||||
|
||||
//int prior = -1;
|
||||
//if(ship !is null || pl !is null) {
|
||||
//if(obj.name.contains_nocase(text)) {
|
||||
//if(pl !is null)
|
||||
//prior = text.length > 3 ? 16 : 4;
|
||||
//else
|
||||
//prior = text.length > 3 ? 6 : 4;
|
||||
//}
|
||||
//if(obj.name.equals_nocase(text))
|
||||
//prior += 100;
|
||||
//}
|
||||
|
||||
//if(WouldInsert(amount, prior, priorities)) {
|
||||
//GoItem@ it;
|
||||
//if(pl !is null)
|
||||
//@it = GoPlanet(obj);
|
||||
//else if(ship !is null)
|
||||
//@it = GoShip(obj);
|
||||
|
||||
//if(it !is null)
|
||||
//GoInsert(it, amount, prior, list, priorities);
|
||||
//}
|
||||
//}
|
||||
|
||||
//Search research
|
||||
/*cnt = getResearchNodeCount();*/
|
||||
/*for(uint i = 0; i < cnt; ++i) {*/
|
||||
/* ResearchNode@ node = getResearchNode(i);*/
|
||||
/* if(node.tech.name.contains_nocase(text)) {*/
|
||||
/* int p = text.length > 3 ? 7 : 1;*/
|
||||
/* if(node.tech.name.equals_nocase(text))*/
|
||||
/* p += 100;*/
|
||||
/* if(!node.unlocked)*/
|
||||
/* p += 1;*/
|
||||
|
||||
/* if(WouldInsert(amount, p, priorities))*/
|
||||
/* GoInsert(GoTechnology(node), amount, p, list, priorities);*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
}
|
||||
|
||||
class GoDialog : BaseGuiElement {
|
||||
GuiTextbox@ box;
|
||||
GoItem@[] items;
|
||||
uint selected;
|
||||
int hovered;
|
||||
|
||||
GoDialog() {
|
||||
super(null, recti());
|
||||
alignment.left.set(AS_Left, 0.1f, 0);
|
||||
alignment.right.set(AS_Right, 0.1f, 0);
|
||||
alignment.top.set(AS_Top, 0.5f, -32 - int(ITEM_COUNT * 15));
|
||||
alignment.bottom.set(AS_Top, 0.5f, int(ITEM_COUNT * 15) + 40);
|
||||
|
||||
@box = GuiTextbox(this, recti(0, 0, 100, 48), "");
|
||||
box.font = FT_Big;
|
||||
@box.alignment = Alignment(Left+8, Top+8, Right-8, Top+56);
|
||||
|
||||
selected = 0;
|
||||
hovered = -1;
|
||||
items.length = ITEM_COUNT;
|
||||
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
box.text = "";
|
||||
for(uint i = 0; i < ITEM_COUNT; ++i)
|
||||
@items[i] = null;
|
||||
selected = 0;
|
||||
hovered = -1;
|
||||
}
|
||||
|
||||
void hide() {
|
||||
visible = false;
|
||||
clear();
|
||||
}
|
||||
|
||||
void next() {
|
||||
if(selected < ITEM_COUNT - 1) {
|
||||
if(items[selected + 1] !is null)
|
||||
selected += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void prev() {
|
||||
if(selected > 0)
|
||||
selected -= 1;
|
||||
}
|
||||
|
||||
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
||||
switch(event.type) {
|
||||
case KET_Key_Down:
|
||||
if(event.key == KEY_UP) {
|
||||
prev();
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_DOWN) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_TAB) {
|
||||
if(shiftKey)
|
||||
prev();
|
||||
else
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_ESC) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KET_Key_Up:
|
||||
if(event.key == KEY_ESC) {
|
||||
hide();
|
||||
setGuiFocus(ActiveTab);
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_UP) {
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_DOWN) {
|
||||
return true;
|
||||
}
|
||||
else if(event.key == KEY_TAB) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return BaseGuiElement::onKeyEvent(event, source);
|
||||
}
|
||||
|
||||
void updateHovered() {
|
||||
vec2i rel = mousePos - AbsolutePosition.topLeft;
|
||||
if(rel.x < 4 || rel.x > size.width - 4) {
|
||||
hovered = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
hovered = (rel.y - 64) / 30;
|
||||
if(hovered < 0 || hovered >= ITEM_COUNT || items[hovered] is null) {
|
||||
hovered = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
switch(event.type) {
|
||||
case GUI_Focus_Lost:
|
||||
if(event.other is null || !event.other.isChildOf(this)) {
|
||||
hide();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GUI_Changed:
|
||||
search();
|
||||
break;
|
||||
case GUI_Confirmed:
|
||||
if(box.text != "" && items[selected] !is null)
|
||||
items[selected].go();
|
||||
hide();
|
||||
if(getGuiFocus() !is null && getGuiFocus().isChildOf(this))
|
||||
setGuiFocus(ActiveTab);
|
||||
return true;
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
||||
if(source is this) {
|
||||
switch(event.type) {
|
||||
case MET_Button_Up:
|
||||
if(hovered >= 0)
|
||||
items[hovered].go();
|
||||
hide();
|
||||
return true;
|
||||
case MET_Moved:
|
||||
updateHovered();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
void search() {
|
||||
GoSearch(box.text, items);
|
||||
selected = 0;
|
||||
hovered = -1;
|
||||
}
|
||||
|
||||
void drawItem(uint i, const recti& absPos) {
|
||||
GoItem@ item = items[i];
|
||||
|
||||
uint flags = SF_Normal;
|
||||
if(i == selected)
|
||||
flags |= SF_Active;
|
||||
if(int(i) == hovered)
|
||||
flags |= SF_Hovered;
|
||||
|
||||
skin.draw(SS_GoItem, flags, absPos);
|
||||
|
||||
recti iconPos = recti_area(
|
||||
absPos.topLeft + vec2i(4, 6),
|
||||
vec2i(20, 18));
|
||||
|
||||
Sprite sprt = item.icon;
|
||||
|
||||
if(sprt.valid)
|
||||
sprt.draw(iconPos);
|
||||
else if(item.skinIcon != SS_NULL)
|
||||
skin.draw(item.skinIcon, SF_Normal, iconPos);
|
||||
|
||||
skin.draw(FT_Medium, absPos.topLeft + vec2i(28, 4), item.text);
|
||||
skin.draw(FT_Medium, absPos.botRight - vec2i(204, 26), item.type);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
//Draw the background
|
||||
skin.draw(SS_GoDialog, SF_Normal, AbsolutePosition);
|
||||
|
||||
//Draw the items
|
||||
for(uint i = 0; i < ITEM_COUNT; ++i) {
|
||||
if(items[i] is null)
|
||||
break;
|
||||
drawItem(i, recti_area(
|
||||
AbsolutePosition.topLeft + vec2i(8, 64 + i * 30),
|
||||
vec2i(size.width - 16, 30)));
|
||||
}
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
void gogogo(bool pressed) {
|
||||
if(pressed) {
|
||||
dialog.visible = true;
|
||||
dialog.bringToFront();
|
||||
setGuiFocus(dialog.box);
|
||||
}
|
||||
}
|
||||
|
||||
void toggleGoDialog() {
|
||||
if(!dialog.visible) {
|
||||
dialog.visible = true;
|
||||
dialog.bringToFront();
|
||||
setGuiFocus(dialog.box);
|
||||
}
|
||||
else {
|
||||
dialog.hide();
|
||||
}
|
||||
}
|
||||
|
||||
GoDialog@ dialog;
|
||||
IGuiElement@ getGoDialog() {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
void init() {
|
||||
//Initialize dialog
|
||||
@dialog = GoDialog();
|
||||
dialog.visible = false;
|
||||
|
||||
//Initialize static items
|
||||
staticItems.insertLast(GoResearch());
|
||||
staticItems.insertLast(GoDesigns());
|
||||
staticItems.insertLast(GoGalaxy());
|
||||
staticItems.insertLast(GoConsole());
|
||||
|
||||
//Add systems to static items
|
||||
for(uint i = 0, cnt = systemCount; i < cnt; ++i)
|
||||
staticItems.insertLast(GoSystem(getSystem(i)));
|
||||
|
||||
//Add resource finds
|
||||
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
|
||||
auto@ type = getResource(i);
|
||||
if(!type.artificial)
|
||||
staticItems.insertLast(GoResource(type));
|
||||
}
|
||||
|
||||
//Add resource classes
|
||||
for(uint i = 0, cnt = getResourceClassCount(); i < cnt; ++i) {
|
||||
auto@ type = getResourceClass(i);
|
||||
staticItems.insertLast(GoResourceClass(type));
|
||||
}
|
||||
|
||||
keybinds::Global.addBind(KB_GO_MENU, "gogogo");
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
#priority init 90
|
||||
import systems;
|
||||
|
||||
enum ScopeType {
|
||||
ST_Invalid,
|
||||
ST_System,
|
||||
|
||||
ST_COUNT
|
||||
};
|
||||
|
||||
class Scope {
|
||||
int id;
|
||||
|
||||
Scope() {
|
||||
id = -1;
|
||||
}
|
||||
|
||||
ScopeType get_type() {
|
||||
return ST_Invalid;
|
||||
}
|
||||
|
||||
vec3d get_position() {
|
||||
return vec3d();
|
||||
}
|
||||
|
||||
string get_name() {
|
||||
return "---";
|
||||
}
|
||||
|
||||
double get_radius() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int get_priority() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class SystemScope : Scope {
|
||||
SystemDesc@ sys;
|
||||
|
||||
SystemScope(SystemDesc@ system) {
|
||||
@sys = system;
|
||||
id = -1;
|
||||
}
|
||||
|
||||
ScopeType get_type() {
|
||||
return ST_System;
|
||||
}
|
||||
|
||||
vec3d get_position() {
|
||||
return sys.position;
|
||||
}
|
||||
|
||||
double get_radius() {
|
||||
return sys.radius;
|
||||
}
|
||||
|
||||
int get_priority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
string get_name() {
|
||||
return sys.object.name;
|
||||
}
|
||||
};
|
||||
|
||||
class ScopeSearch {
|
||||
int minPriority;
|
||||
int maxPriority;
|
||||
double priorityFactor;
|
||||
double minDistance;
|
||||
double maxDistance;
|
||||
bool[] allowedTypes;
|
||||
|
||||
vec3d position;
|
||||
Scope@[] results;
|
||||
double[] factors;
|
||||
|
||||
ScopeSearch() {
|
||||
minPriority = INT_MIN;
|
||||
maxPriority = INT_MAX;
|
||||
priorityFactor = 1.0;
|
||||
minDistance = -INFINITY;
|
||||
maxDistance = INFINITY;
|
||||
|
||||
allowedTypes.length = ST_COUNT;
|
||||
for(int i = 0; i < ST_COUNT; ++i)
|
||||
allowedTypes[i] = true;
|
||||
}
|
||||
};
|
||||
|
||||
namespace scopes {
|
||||
::map scopes;
|
||||
int nextScopeId = 1;
|
||||
};
|
||||
|
||||
void init() {
|
||||
//Add systems as scopes
|
||||
for(uint i = 0, cnt = systemCount; i < cnt; ++i)
|
||||
addScope(SystemScope(getSystem(i)));
|
||||
}
|
||||
|
||||
int addScope(Scope@ scope) {
|
||||
if(scope.id <= 0)
|
||||
scope.id = scopes::nextScopeId++;
|
||||
scopes::scopes.set(scope.id, @scope);
|
||||
return scope.id;
|
||||
}
|
||||
|
||||
void removeScope(Scope@ scope) {
|
||||
removeScope(scope.id);
|
||||
}
|
||||
|
||||
void removeScope(int id) {
|
||||
if(id > 0)
|
||||
scopes::scopes.delete(id);
|
||||
}
|
||||
|
||||
uint searchScopes(ScopeSearch@ s, uint amount) {
|
||||
uint found = 0;
|
||||
s.results.length = amount;
|
||||
s.factors.length = amount;
|
||||
double maxFactor = 0.0;
|
||||
|
||||
map_iterator it = scopes::scopes.iterator();
|
||||
Scope@ scope;
|
||||
while(it.iterate(@scope)) {
|
||||
int priority = scope.priority;
|
||||
if(priority < s.minPriority || priority > s.maxPriority)
|
||||
continue;
|
||||
|
||||
double factor = s.position.distanceTo(scope.position);
|
||||
factor -= scope.radius;
|
||||
if(factor < s.minDistance || factor > s.maxDistance)
|
||||
continue;
|
||||
if(priority != 0)
|
||||
factor /= double(priority) * s.priorityFactor;
|
||||
|
||||
if(found < amount) {
|
||||
if(found == 0) {
|
||||
@s.results[0] = scope;
|
||||
s.factors[0] = factor;
|
||||
}
|
||||
else {
|
||||
for(int i = found; i >= 0; --i) {
|
||||
if(i == 0 || factor > s.factors[i-1]) {
|
||||
@s.results[i] = scope;
|
||||
s.factors[i] = factor;
|
||||
break;
|
||||
}
|
||||
@s.results[i] = s.results[i-1];
|
||||
s.factors[i] = s.factors[i-1];
|
||||
}
|
||||
}
|
||||
maxFactor = s.factors[found];
|
||||
++found;
|
||||
}
|
||||
else if(factor < maxFactor) {
|
||||
for(int i = amount - 1; i >= 0; --i) {
|
||||
if(i == 0 || factor > s.factors[i-1]) {
|
||||
@s.results[i] = scope;
|
||||
s.factors[i] = factor;
|
||||
break;
|
||||
}
|
||||
@s.results[i] = s.results[i-1];
|
||||
s.factors[i] = s.factors[i-1];
|
||||
}
|
||||
maxFactor = s.factors[amount-1];
|
||||
}
|
||||
}
|
||||
|
||||
if(found != amount) {
|
||||
s.results.length = found;
|
||||
s.factors.length = found;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
#priority init 10
|
||||
import navigation.scopes;
|
||||
import navigation.SmartCamera;
|
||||
from input import activeCamera;
|
||||
import elements.BaseGuiElement;
|
||||
|
||||
const int TOPBAR_OFFSET = 77;
|
||||
const int SCOPE_SPACING = 4;
|
||||
const int SCOPE_WIDTH = 50;
|
||||
const int SCOPE_HEIGHT = 50;
|
||||
const int SCOPE_PADDING = 2;
|
||||
|
||||
const double SEARCH_INTERVAL = 1.0;
|
||||
const uint MAX_SCOPES = 16;
|
||||
|
||||
Color scopeColor(0xffffff40);
|
||||
Color scopeHoverColor(0x00ff0040);
|
||||
Color scopeActiveColor(0xff000040);
|
||||
Color scopePanningColor(0x0000ff40);
|
||||
string ellipsis = "-";
|
||||
|
||||
const double smartPanSpeed = 10000.0;
|
||||
const double fadeDistanceMin = 10000.0;
|
||||
const double fadeDistanceMax = 16000.0;
|
||||
|
||||
ScopeSearch search;
|
||||
double searchTimer = 0.0;
|
||||
ScopeButton@ panningButton;
|
||||
ScopeButton@[] buttons;
|
||||
bool shown = false;
|
||||
uint displayedScopes = 0;
|
||||
|
||||
void init() {
|
||||
search.maxDistance = 16000.0;
|
||||
|
||||
@panningButton = ScopeButton();
|
||||
panningButton.visible = false;
|
||||
|
||||
buttons.length = MAX_SCOPES;
|
||||
for(uint i = 0; i < MAX_SCOPES; ++i) {
|
||||
@buttons[i] = ScopeButton();
|
||||
buttons[i].visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void showSmartPan() {
|
||||
shown = true;
|
||||
update();
|
||||
searchTimer = SEARCH_INTERVAL;
|
||||
}
|
||||
|
||||
void hideSmartPan() {
|
||||
shown = false;
|
||||
for(uint i = 0; i < MAX_SCOPES; ++i)
|
||||
buttons[i].visible = false;
|
||||
}
|
||||
|
||||
void toggleSmartPan() {
|
||||
if(shown)
|
||||
hideSmartPan();
|
||||
else
|
||||
showSmartPan();
|
||||
}
|
||||
|
||||
void update() {
|
||||
search.position = activeCamera.lookAt;
|
||||
displayedScopes = searchScopes(search, MAX_SCOPES);
|
||||
|
||||
for(uint i = 0; i < displayedScopes; ++i) {
|
||||
Scope@ scope = search.results[i];
|
||||
|
||||
//Ignore current scope
|
||||
if(search.position.distanceTo(scope.position) < scope.radius
|
||||
|| (panningButton.visible && panningButton.scope is scope)) {
|
||||
buttons[i].visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Ready the scope button
|
||||
buttons[i].visible = true;
|
||||
buttons[i].set(scope);
|
||||
buttons[i].updatePosition();
|
||||
}
|
||||
|
||||
//Hide extra buttons
|
||||
for(uint i = displayedScopes; i < MAX_SCOPES; ++i)
|
||||
buttons[i].visible = false;
|
||||
}
|
||||
|
||||
void updatePositions() {
|
||||
for(uint i = 0; i < displayedScopes; ++i) {
|
||||
Scope@ scope = buttons[i].scope;
|
||||
if(scope is null || search.position.distanceTo(scope.position) < scope.radius
|
||||
|| (panningButton.visible && panningButton.scope is scope)) {
|
||||
buttons[i].visible = false;
|
||||
}
|
||||
else {
|
||||
buttons[i].visible = true;
|
||||
buttons[i].updatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool waitForMove = true;
|
||||
int buttonAlpha = 80;
|
||||
void tick(double time) {
|
||||
if(!shown)
|
||||
return;
|
||||
|
||||
//Fade the buttons out at large distances
|
||||
double dist = activeCamera.distance;
|
||||
if(dist > fadeDistanceMin) {
|
||||
if(dist >= fadeDistanceMax) {
|
||||
buttonAlpha = 0;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
buttonAlpha = 80 - ceil(80.0 * (dist - fadeDistanceMin) / (fadeDistanceMax - fadeDistanceMin));
|
||||
}
|
||||
}
|
||||
else {
|
||||
buttonAlpha = 80;
|
||||
}
|
||||
|
||||
//Check if we're panning with a button
|
||||
vec2i mpos = mousePos;
|
||||
if(buttonAlpha > 0 && mpos.x < 6 || mpos.x >= screenSize.width - 6) {
|
||||
if(!waitForMove) {
|
||||
bool found = false;
|
||||
if(panningButton.visible) {
|
||||
if(!panningButton.AbsolutePosition.isWithin(mpos)) {
|
||||
panningButton.visible = false;
|
||||
}
|
||||
else {
|
||||
double tickPan = time * smartPanSpeed;
|
||||
if(activeCamera.panTo(panningButton.scope.position, tickPan)) {
|
||||
panningButton.visible = false;
|
||||
waitForMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(uint i = 0; i < displayedScopes; ++i) {
|
||||
if(buttons[i].visible && buttons[i].AbsolutePosition.isWithin(mpos)) {
|
||||
buttons[i].visible = false;
|
||||
panningButton.set(buttons[i].scope);
|
||||
panningButton.AbsolutePosition = buttons[i].AbsolutePosition;
|
||||
panningButton.AbsoluteClipRect = buttons[i].AbsoluteClipRect;
|
||||
panningButton.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
panningButton.visible = false;
|
||||
waitForMove = false;
|
||||
}
|
||||
|
||||
//Update the buttons
|
||||
//if(searchTimer <= 0.0) {
|
||||
//searchTimer = SEARCH_INTERVAL;
|
||||
update();
|
||||
//}
|
||||
//else {
|
||||
//updatePositions();
|
||||
//searchTimer -= time;
|
||||
//}
|
||||
}
|
||||
|
||||
class ScopeButton : BaseGuiElement {
|
||||
Scope@ scope;
|
||||
string name;
|
||||
bool left;
|
||||
bool Hovered;
|
||||
bool Pressed;
|
||||
|
||||
ScopeButton() {
|
||||
Hovered = false;
|
||||
Pressed = false;
|
||||
super(null, recti(0, 0, SCOPE_WIDTH, SCOPE_HEIGHT));
|
||||
}
|
||||
|
||||
void updatePosition() {
|
||||
vec3d camPos = activeCamera.lookAt;
|
||||
vec3d scopePos = scope.position;
|
||||
double flatAngle = activeCamera.screenAngle(scopePos);
|
||||
double percentage = 0.0;
|
||||
|
||||
vec2i pos();
|
||||
if(flatAngle >= 0 && flatAngle <= 0.5) {
|
||||
left = false;
|
||||
percentage = 0.5 - flatAngle;
|
||||
}
|
||||
else if(flatAngle > 0.5 && flatAngle <= 1.0) {
|
||||
left = true;
|
||||
percentage = flatAngle - 0.5;
|
||||
}
|
||||
else if(flatAngle < 0 && flatAngle >= -0.5) {
|
||||
left = false;
|
||||
percentage = 0.5 + flatAngle * -1.0;
|
||||
}
|
||||
else if(flatAngle < -0.5 && flatAngle >= -1.0) {
|
||||
left = true;
|
||||
percentage = 1.5 + flatAngle;
|
||||
}
|
||||
|
||||
if(left)
|
||||
pos.x = 0;
|
||||
else
|
||||
pos.x = screenSize.width - size.width;
|
||||
|
||||
pos.y = TOPBAR_OFFSET;
|
||||
pos.y += double(screenSize.height - TOPBAR_OFFSET - SCOPE_HEIGHT) * percentage;
|
||||
|
||||
bool overlap;
|
||||
do {
|
||||
overlap = false;
|
||||
recti box = recti_area(pos, size);
|
||||
if(panningButton.visible && panningButton.AbsolutePosition.overlaps(box)) {
|
||||
overlap = true;
|
||||
pos.y = panningButton.AbsolutePosition.topLeft.y;
|
||||
|
||||
if(percentage < 0.5)
|
||||
pos.y += SCOPE_HEIGHT + SCOPE_SPACING;
|
||||
else
|
||||
pos.y -= SCOPE_HEIGHT + SCOPE_SPACING;
|
||||
continue;
|
||||
}
|
||||
|
||||
for(uint i = 0; i < MAX_SCOPES; ++i) {
|
||||
if(buttons[i] is this)
|
||||
break;
|
||||
if(buttons[i].left == left && buttons[i].AbsolutePosition.overlaps(box)) {
|
||||
overlap = true;
|
||||
pos.y = buttons[i].AbsolutePosition.topLeft.y;
|
||||
|
||||
if(percentage < 0.5)
|
||||
pos.y += SCOPE_HEIGHT + SCOPE_SPACING;
|
||||
else
|
||||
pos.y -= SCOPE_HEIGHT + SCOPE_SPACING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(pos.y < 0 || pos.y + SCOPE_HEIGHT > screenSize.height) {
|
||||
visible = false;
|
||||
return;
|
||||
}
|
||||
} while(overlap);
|
||||
|
||||
position = pos;
|
||||
}
|
||||
|
||||
void set(Scope@ s) {
|
||||
@scope = s;
|
||||
name = scope.name;
|
||||
}
|
||||
|
||||
void trigger() {
|
||||
activeCamera.zoomTo(scope.position);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.caller is this && buttonAlpha > 0) {
|
||||
switch(event.type) {
|
||||
case GUI_Mouse_Entered:
|
||||
Hovered = true;
|
||||
break;
|
||||
case GUI_Mouse_Left:
|
||||
Hovered = false;
|
||||
Pressed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
||||
if(source is this && buttonAlpha > 0) {
|
||||
switch(event.type) {
|
||||
case MET_Button_Down:
|
||||
if(event.button == 0) {
|
||||
Pressed = true;
|
||||
}
|
||||
return true;
|
||||
case MET_Button_Up:
|
||||
if(event.button == 0 && Pressed) {
|
||||
Pressed = false;
|
||||
trigger();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(buttonAlpha <= 0)
|
||||
return;
|
||||
|
||||
Color col = scopeColor;
|
||||
if(panningButton is this)
|
||||
col = scopePanningColor;
|
||||
else if(Pressed)
|
||||
col = scopeActiveColor;
|
||||
else if(Hovered && !panningButton.visible)
|
||||
col = scopeHoverColor;
|
||||
|
||||
col.a = buttonAlpha;
|
||||
drawRectangle(AbsolutePosition, col);
|
||||
|
||||
const Font@ fnt = skin.getFont(FT_Normal);
|
||||
recti textPos = AbsolutePosition;
|
||||
textPos.topLeft.x += SCOPE_PADDING;
|
||||
textPos.botRight.x -= SCOPE_PADDING;
|
||||
|
||||
int vpadd = (textPos.height - fnt.getLineHeight()) / 2;
|
||||
textPos.topLeft.y += vpadd;
|
||||
textPos.botRight.y -= vpadd;
|
||||
|
||||
fnt.draw(textPos, name, ellipsis);
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
#priority init 100
|
||||
from settings.map_lib import SystemDesc;
|
||||
import void recalculateElevation() from "navigation.elevation";
|
||||
import void promptExtentRefresh() from "tabs.GalaxyTab";
|
||||
|
||||
SystemDesc[] Systems;
|
||||
|
||||
uint get_systemCount() {
|
||||
return Systems.length;
|
||||
}
|
||||
|
||||
SystemDesc@ getSystem(uint index) {
|
||||
return Systems[index];
|
||||
}
|
||||
|
||||
SystemDesc@ getSystem(Region@ region) {
|
||||
if(region is null || region.SystemId == -1)
|
||||
return null;
|
||||
return Systems[region.SystemId];
|
||||
}
|
||||
|
||||
SystemDesc@ getSystem(const string& name) {
|
||||
//TODO: Use dictionary
|
||||
uint cnt = systemCount;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
if(getSystem(i).name == name)
|
||||
return getSystem(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SystemDesc@ getSystem(const vec3d& point) {
|
||||
//TODO: This really should not be O(n), implement a better
|
||||
//structure for these lookups
|
||||
for(uint i = 0, cnt = Systems.length; i < cnt; ++i) {
|
||||
SystemDesc@ sys = Systems[i];
|
||||
if(point.distanceToSQ(sys.position) < sys.radius * sys.radius)
|
||||
return sys;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool loaded = false, doRefresh = false;
|
||||
void init() {
|
||||
if(loaded)
|
||||
return;
|
||||
Systems.syncFrom(getSystems());
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
if(doRefresh) {
|
||||
Systems.length = 0;
|
||||
Systems.syncFrom(getSystems());
|
||||
doRefresh = false;
|
||||
promptExtentRefresh();
|
||||
recalculateElevation();
|
||||
}
|
||||
}
|
||||
|
||||
void save(SaveFile& data) {
|
||||
data << uint(Systems.length);
|
||||
for(uint i = 0; i < Systems.length; ++i)
|
||||
Systems[i].save(data);
|
||||
}
|
||||
|
||||
void load(SaveFile& data) {
|
||||
loaded = true;
|
||||
uint count = 0;
|
||||
data >> count;
|
||||
Systems.length = count;
|
||||
for(uint i = 0; i < Systems.length; ++i)
|
||||
Systems[i].load(data);
|
||||
}
|
||||
|
||||
void refreshClientSystems() {
|
||||
doRefresh = true;
|
||||
}
|
||||
Reference in New Issue
Block a user