Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
const double DROPOFF_FACTOR = 64.0;
|
||||
const double DROPOFF_FACTOR_SQ = DROPOFF_FACTOR * DROPOFF_FACTOR;
|
||||
|
||||
final class SoundEntry {
|
||||
vec3d pos;
|
||||
double scale;
|
||||
};
|
||||
|
||||
final class AmbienceGroup {
|
||||
map entries;
|
||||
SoundEntry@ active;
|
||||
const SoundSource@ source;
|
||||
Sound@ sound;
|
||||
double volume = 0;
|
||||
|
||||
AmbienceGroup(const SoundSource& soundSource) {
|
||||
@source = soundSource;
|
||||
}
|
||||
|
||||
void addEntry(int id, const vec3d& pos, double scale) {
|
||||
SoundEntry entry;
|
||||
entry.pos = pos;
|
||||
entry.scale = scale;
|
||||
entries.set(id, @entry);
|
||||
}
|
||||
|
||||
void deleteEntry(int id) {
|
||||
entries.delete(id);
|
||||
}
|
||||
|
||||
SoundEntry@ findNearest(const vec3d& to) {
|
||||
SoundEntry@ nearest;
|
||||
double nearDist = 0;
|
||||
|
||||
map_iterator i = entries.iterator();
|
||||
SoundEntry@ entry;
|
||||
while(i.iterate(@entry)) {
|
||||
double d = to.distanceToSQ(entry.pos);
|
||||
if(d < entry.scale * entry.scale * DROPOFF_FACTOR_SQ && (nearest is null || d < nearDist)) {
|
||||
@nearest = entry;
|
||||
nearDist = d;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
void tick(double time, const vec3d& pos) {
|
||||
auto@ entry = findNearest(pos);
|
||||
if(entry !is active) {
|
||||
if(active !is null && sound !is null) {
|
||||
if(volume > 0) {
|
||||
volume = max(volume - time, 0.0);
|
||||
sound.volume = volume * sqrt(active.scale);
|
||||
}
|
||||
else {
|
||||
sound.pause();
|
||||
@active = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@active = entry;
|
||||
}
|
||||
}
|
||||
else if(active !is null) {
|
||||
if(sound is null) {
|
||||
@sound = source.play(entry.pos, loop=true, pause=true, priority=true);
|
||||
if(sound !is null) {
|
||||
sound.minDistance = entry.scale;
|
||||
sound.maxDistance = entry.scale * DROPOFF_FACTOR;
|
||||
sound.volume = 0;
|
||||
sound.resume();
|
||||
volume = 0;
|
||||
}
|
||||
}
|
||||
else if(volume < 1.0) {
|
||||
if(volume == 0.0) {
|
||||
sound.position = entry.pos;
|
||||
sound.minDistance = entry.scale;
|
||||
sound.maxDistance = entry.scale * DROPOFF_FACTOR;
|
||||
sound.resume();
|
||||
}
|
||||
volume = min(1.0, volume+time);
|
||||
sound.volume = volume * sqrt(entry.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if(sound !is null)
|
||||
sound.stop();
|
||||
entries.deleteAll();
|
||||
}
|
||||
};
|
||||
|
||||
Mutex mtx;
|
||||
dictionary ambientSounds;
|
||||
|
||||
void addAmbientSource(string sound, int id, vec3d pos, double scale) {
|
||||
Lock lck(mtx);
|
||||
AmbienceGroup@ group;
|
||||
if(!ambientSounds.get(sound, @group)) {
|
||||
const auto@ source = getSound(sound);
|
||||
if(source is null)
|
||||
return;
|
||||
@group = AmbienceGroup(source);
|
||||
ambientSounds.set(sound, @group);
|
||||
}
|
||||
|
||||
group.addEntry(id, pos, scale);
|
||||
}
|
||||
|
||||
void removeAmbientSource(int id) {
|
||||
Lock lck(mtx);
|
||||
AmbienceGroup@ group;
|
||||
dictionary_iterator i = ambientSounds.iterator();
|
||||
while(i.iterate(@group))
|
||||
group.deleteEntry(id);
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
Lock lck(mtx);
|
||||
AmbienceGroup@ group;
|
||||
dictionary_iterator i = ambientSounds.iterator();
|
||||
while(i.iterate(@group))
|
||||
group.tick(time, cameraPos);
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
Lock lck(mtx);
|
||||
AmbienceGroup@ group;
|
||||
dictionary_iterator i = ambientSounds.iterator();
|
||||
while(i.iterate(@group))
|
||||
group.clear();
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
from obj_selection import selectedObject, selectedObjects, hoveredObject;
|
||||
from input import activeCamera;
|
||||
import orbitals;
|
||||
import influence;
|
||||
|
||||
bool get_cheats() {
|
||||
if(!getCheatsEnabled()) {
|
||||
error("ERROR: Cheats not enabled. Use 'cheats on' to enable.");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class SetCheats : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
setCheatsEnabled(args.length == 0 || toBool(args));
|
||||
}
|
||||
};
|
||||
|
||||
class ColonizeCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
cheatColonize(args.length == 0 || toBool(args));
|
||||
}
|
||||
};
|
||||
|
||||
class SeeAllCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
cheatSeeAll(args.length == 0 || toBool(args));
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnFlagshipCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int space = args.findFirst(" ");
|
||||
if(args.length == 0 || space == -1) {
|
||||
error("Usage: ch_flagship <empire id> <design name>");
|
||||
return;
|
||||
}
|
||||
|
||||
string strId = args.substr(0, space);
|
||||
string strDesign = args.substr(space+1, args.length - space - 1);
|
||||
Empire@ emp;
|
||||
if(strId == "*")
|
||||
@emp = playerEmpire;
|
||||
else
|
||||
@emp = getEmpireByID(toUInt(strId));
|
||||
if(emp is null) {
|
||||
error("ERROR: Cannot find empire with id "+strId);
|
||||
return;
|
||||
}
|
||||
|
||||
const Design@ dsg = emp.getDesign(strDesign);
|
||||
if(dsg is null) {
|
||||
error("ERROR: Cannot find design with name "+strDesign);
|
||||
return;
|
||||
}
|
||||
if(dsg.hasTag(ST_IsSupport)) {
|
||||
error("ERROR: Design "+strDesign+" is not a flagship.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(selectedObject is null) {
|
||||
if(activeCamera !is null)
|
||||
cheatSpawnFlagship(activeCamera.screenToPoint(mousePos), dsg, emp);
|
||||
}
|
||||
else {
|
||||
cheatSpawnFlagship(selectedObject, dsg, emp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnSupportCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int space = args.findFirst(" ");
|
||||
if(args.length == 0 || space == -1) {
|
||||
error("Usage: ch_supports <amount> <design name>");
|
||||
return;
|
||||
}
|
||||
|
||||
Empire@ emp = playerEmpire;
|
||||
if(selectedObject !is null)
|
||||
@emp = selectedObject.owner;
|
||||
else if(hoveredObject !is null)
|
||||
@emp = hoveredObject.owner;
|
||||
|
||||
string strAmount = args.substr(0, space);
|
||||
string strDesign = args.substr(space+1, args.length - space - 1);
|
||||
uint amount = clamp(toUInt(strAmount), 0, 999999);
|
||||
|
||||
const Design@ dsg = emp.getDesign(strDesign);
|
||||
if(dsg is null) {
|
||||
error("ERROR: Cannot find design with name "+strDesign);
|
||||
return;
|
||||
}
|
||||
if(!dsg.hasTag(ST_IsSupport)) {
|
||||
error("ERROR: Design "+strDesign+" is not a support ship.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(selectedObject !is null) {
|
||||
cheatSpawnSupports(selectedObject, dsg, amount);
|
||||
}
|
||||
else if(hoveredObject !is null) {
|
||||
cheatSpawnSupports(hoveredObject, dsg, amount);
|
||||
}
|
||||
else {
|
||||
if(activeCamera !is null)
|
||||
cheatSpawnSupports(activeCamera.screenToPoint(mousePos), dsg, amount, playerEmpire);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnOrbitalCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int space = args.findFirst(" ");
|
||||
if(args.length == 0 || space == -1) {
|
||||
error("Usage: ch_orbital <empire id> <orbital ident>");
|
||||
return;
|
||||
}
|
||||
|
||||
string strId = args.substr(0, space);
|
||||
string strOrbital = args.substr(space+1, args.length - space - 1);
|
||||
|
||||
Empire@ emp;
|
||||
if(strId == "*")
|
||||
@emp = playerEmpire;
|
||||
else
|
||||
@emp = getEmpireByID(toUInt(strId));
|
||||
|
||||
if(emp is null) {
|
||||
error("ERROR: Cannot find empire with id "+strId);
|
||||
return;
|
||||
}
|
||||
|
||||
const OrbitalModule@ def = getOrbitalModule(strOrbital);
|
||||
if(def is null) {
|
||||
error("ERROR: Cannot find orbital with name "+strOrbital);
|
||||
return;
|
||||
}
|
||||
|
||||
if(activeCamera !is null)
|
||||
cheatSpawnOrbital(activeCamera.screenToPoint(mousePos), def.id, emp);
|
||||
}
|
||||
};
|
||||
|
||||
class TriggerCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
Empire@ emp = playerEmpire;
|
||||
Object@ obj;
|
||||
if(selectedObject !is null)
|
||||
@obj = selectedObject;
|
||||
else if(hoveredObject !is null)
|
||||
@obj = hoveredObject;
|
||||
|
||||
cheatTrigger(obj, emp, args);
|
||||
}
|
||||
};
|
||||
|
||||
class InfluenceCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatInfluence(toInt(args));
|
||||
}
|
||||
};
|
||||
|
||||
class ResearchCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatResearch(toDouble(args));
|
||||
}
|
||||
};
|
||||
|
||||
class MoneyCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatMoney(toInt(args));
|
||||
}
|
||||
};
|
||||
|
||||
class FTLCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatFTL(toInt(args));
|
||||
}
|
||||
};
|
||||
|
||||
class EnergyCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatEnergy(toInt(args));
|
||||
}
|
||||
};
|
||||
|
||||
class DestroyCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
if(selectedObject !is null) {
|
||||
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i)
|
||||
cheatDestroy(selectedObjects[i]);
|
||||
}
|
||||
else {
|
||||
cheatDestroy(hoveredObject);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LaborCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
double amount = toDouble(args);
|
||||
if(selectedObject !is null) {
|
||||
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i)
|
||||
cheatLabor(selectedObjects[i], amount);
|
||||
}
|
||||
else {
|
||||
cheatLabor(hoveredObject, amount);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class OwnerCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
Empire@ emp = getEmpireByID(toUInt(args));
|
||||
if(emp is null) {
|
||||
error("ERROR: Cannot find empire with id "+args);
|
||||
return;
|
||||
}
|
||||
|
||||
if(selectedObject !is null) {
|
||||
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i)
|
||||
cheatChangeOwner(selectedObjects[i], emp);
|
||||
}
|
||||
else {
|
||||
cheatChangeOwner(hoveredObject, emp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AICheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
cheatActivateAI();
|
||||
}
|
||||
};
|
||||
|
||||
class AIDebug : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int empireID = toInt(args);
|
||||
Empire@ emp = getEmpireByID(empireID);
|
||||
cheatDebugAI(emp);
|
||||
}
|
||||
};
|
||||
|
||||
class AICommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int empireID = toInt(args);
|
||||
int cmdStart = args.findFirst(" ");
|
||||
if(cmdStart > 0) {
|
||||
Empire@ emp = getEmpireByID(empireID);
|
||||
cheatCommandAI(emp, args.substr(cmdStart + 1, args.length() - (cmdStart + 1)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AllianceCheat : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(!cheats)
|
||||
return;
|
||||
|
||||
int space = args.findFirst(" ");
|
||||
if(args.length == 0 || space == -1) {
|
||||
error("Usage: ch_alliance <emp 1 id> <emp 2 id>");
|
||||
return;
|
||||
}
|
||||
|
||||
string str1 = args.substr(0, space);
|
||||
string str2 = args.substr(space+1, args.length - space - 1);
|
||||
|
||||
Empire@ emp1 = getEmpireByID(toInt(str1));
|
||||
if(emp1 is null) {
|
||||
error("No such empire id: "+str1);
|
||||
return;
|
||||
}
|
||||
|
||||
Empire@ emp2 = getEmpireByID(toInt(str2));
|
||||
if(emp2 is null) {
|
||||
error("No such empire id: "+str2);
|
||||
return;
|
||||
}
|
||||
|
||||
cheatAlliance(emp1, emp2);
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("cheats", SetCheats());
|
||||
addConsoleCommand("ch_colonize", ColonizeCheat());
|
||||
addConsoleCommand("ch_seeall", SeeAllCheat());
|
||||
addConsoleCommand("ch_flagship", SpawnFlagshipCheat());
|
||||
addConsoleCommand("ch_supports", SpawnSupportCheat());
|
||||
addConsoleCommand("ch_orbital", SpawnOrbitalCheat());
|
||||
addConsoleCommand("ch_influence", InfluenceCheat());
|
||||
addConsoleCommand("ch_research", ResearchCheat());
|
||||
addConsoleCommand("ch_money", MoneyCheat());
|
||||
addConsoleCommand("ch_destroy", DestroyCheat());
|
||||
addConsoleCommand("ch_labor", LaborCheat());
|
||||
addConsoleCommand("ch_owner", OwnerCheat());
|
||||
addConsoleCommand("ch_activate_ai", AICheat());
|
||||
addConsoleCommand("ch_ftl", FTLCheat());
|
||||
addConsoleCommand("ch_energy", EnergyCheat());
|
||||
addConsoleCommand("ch_debug_ai", AIDebug());
|
||||
addConsoleCommand("ch_ai_command", AICommand());
|
||||
addConsoleCommand("ch_alliance", AllianceCheat());
|
||||
addConsoleCommand("ch_trigger", TriggerCheat());
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
import targeting.ObjectTarget;
|
||||
import resources;
|
||||
from obj_selection import selectedObject, selectedObjects, hoveredObject;
|
||||
import targeting.ObjectTarget;
|
||||
import targeting.PointTarget;
|
||||
from input import activeCamera, mouseToGrid;
|
||||
import void zoomTabTo(Object@ obj) from "tabs.GalaxyTab";
|
||||
import vec3d strategicPosition(Object& obj) from "obj_selection";
|
||||
import void openSupportOverlay(Object@ obj, Object@ to) from "tabs.GalaxyTab";
|
||||
|
||||
array<Object@>@ get_immediateSelection() {
|
||||
auto@ selected = selectedObjects;
|
||||
if(selected.length == 0) {
|
||||
@selected = array<Object@>();
|
||||
Object@ obj = hoveredObject;
|
||||
if(obj !is null)
|
||||
selected.insertLast(obj);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool anySelected(ObjectType ofType = OT_COUNT, bool owned = false, array<Object@>@ list = null) {
|
||||
auto@ objs = list !is null ? list : selectedObjects;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
Object@ obj = objs[i];
|
||||
|
||||
bool passed = true;
|
||||
if(ofType != OT_COUNT && obj.type != ofType)
|
||||
passed = false;
|
||||
if(owned && obj.owner !is playerEmpire)
|
||||
passed = false;
|
||||
if(passed)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class ExportResources : ObjectTargeting {
|
||||
const ResourceType@ res;
|
||||
array<Object@> objs;
|
||||
bool isQueued = false;
|
||||
array<BeamNode@> beams;
|
||||
|
||||
ExportResources(const ResourceType@ type, array<Object@>& sources, bool isTemporary = mouseLeft) {
|
||||
@res = type;
|
||||
this.isTemporary = isTemporary;
|
||||
if(type !is null)
|
||||
icon = type.smallIcon;
|
||||
drawCrosshair = false;
|
||||
validIconColor = colors::White;
|
||||
iconSize = vec2i(32, 32);
|
||||
objs.reserve(sources.length);
|
||||
for(uint i = 0, cnt = sources.length; i < cnt; ++i) {
|
||||
Object@ obj = sources[i];
|
||||
|
||||
if(obj.hasResources) {
|
||||
if(obj.owner !is playerEmpire)
|
||||
isQueued = true;
|
||||
else if(obj.isPlanet) {
|
||||
auto@ type = getResource(obj.primaryResourceType);
|
||||
if(type is null)
|
||||
continue;
|
||||
if(type.level > obj.level)
|
||||
isQueued = true;
|
||||
}
|
||||
objs.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
beams.length = objs.length;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ beam = BeamNode(material::MoveBeam, 0.002f, vec3d(), vec3d(), true);
|
||||
beam.visible = false;
|
||||
if(isQueued)
|
||||
beam.color = Color(0xffe400ff);
|
||||
else
|
||||
beam.color = Color(0x76e0e0ff);
|
||||
@beams[i] = beam;
|
||||
}
|
||||
}
|
||||
|
||||
~ExportResources() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for(uint i = 0, cnt = beams.length; i < cnt; ++i) {
|
||||
beams[i].markForDeletion();
|
||||
@beams[i] = null;
|
||||
}
|
||||
beams.length = 0;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if(isTemporary)
|
||||
call(null);
|
||||
}
|
||||
|
||||
void hover(Object@ target, const vec2i& mouse) {
|
||||
for(uint i = 0, cnt = beams.length; i < cnt; ++i) {
|
||||
auto@ beam = beams[i];
|
||||
beam.position = strategicPosition(objs[i]);
|
||||
if(target !is null && target.hasResources)
|
||||
beam.endPosition = strategicPosition(target);
|
||||
else
|
||||
beam.endPosition = mouseToGrid(mouse);
|
||||
beam.rebuildTransform();
|
||||
beam.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool valid(Object@ target) {
|
||||
if(isTemporary && objs.length == 1 && objs[0] is target)
|
||||
return false;
|
||||
if(target is null || !target.hasResources)
|
||||
return false;
|
||||
if(!target.importEnabled)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
bool anyExported = false;
|
||||
|
||||
if(target is null || target.hasResources) {
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.hasResources) {
|
||||
obj.exportResource(0, obj !is target ? target : null);
|
||||
anyExported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(anyExported)
|
||||
sound::order_goto.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
|
||||
string emptyMessage() {
|
||||
if(res !is null && objs.length <= 1)
|
||||
return format(locale::EXPORT_RESOURCE_PROMPT, res.name);
|
||||
else
|
||||
return locale::EXPORT_RESOURCES_PROMPT;
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) {
|
||||
if(!valid) {
|
||||
if(isTemporary && objs.length == 1 && objs[0] is target)
|
||||
return emptyMessage();
|
||||
return locale::ONLY_PLANETS;
|
||||
}
|
||||
|
||||
if(res !is null && objs.length <= 1) {
|
||||
if(!isQueued && target.owner is playerEmpire)
|
||||
return format(locale::EXPORT_RESOURCE, res.name, target.name);
|
||||
else
|
||||
return format(locale::QUEUE_EXPORT_RESOURCE, res.name, target.name);
|
||||
}
|
||||
else {
|
||||
return format(locale::EXPORT_RESOURCES, target.name);
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Object@ target, bool valid) override {
|
||||
}
|
||||
};
|
||||
|
||||
void doExport(bool pressed) {
|
||||
if(pressed) {
|
||||
auto@ objs = immediateSelection;
|
||||
if(anySelected(ofType=OT_Planet, list=objs) || anySelected(ofType=OT_Asteroid, list=objs)) {
|
||||
const ResourceType@ res;
|
||||
Object@ obj = objs[0];
|
||||
if(obj !is null && obj.hasResources)
|
||||
@res = getResource(obj.primaryResourceType);
|
||||
targetObject(ExportResources(res, objs));
|
||||
}
|
||||
else {
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doCancelExport(bool pressed) {
|
||||
if(pressed) {
|
||||
bool anyCancelled = false;
|
||||
auto@ objs = immediateSelection;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.hasResources) {
|
||||
obj.exportResource(0, null);
|
||||
anyCancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(anyCancelled)
|
||||
sound::order_goto.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
|
||||
void doAutoImport(bool pressed) {
|
||||
if(pressed) {
|
||||
bool anyImported = false;
|
||||
auto@ objs = immediateSelection;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.isPlanet) {
|
||||
auto@ resType = getResource(obj.primaryResourceType);
|
||||
if(resType !is null && resType.level > 0) {
|
||||
playerEmpire.autoImportToLevel(obj, resType.level);
|
||||
anyImported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(anyImported)
|
||||
sound::generic_click.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
|
||||
class TransferSupports : ObjectTargeting {
|
||||
Object@ obj;
|
||||
BeamNode@ beam;
|
||||
|
||||
TransferSupports(Object@ source, bool isTemporary = mouseLeft) {
|
||||
@obj = source;
|
||||
this.isTemporary = isTemporary;
|
||||
icon = icons::ManageSupports;
|
||||
drawCrosshair = false;
|
||||
validIconColor = colors::White;
|
||||
iconSize = vec2i(32, 32);
|
||||
|
||||
@beam = BeamNode(material::MoveBeam, 0.002f, vec3d(), vec3d(), true);
|
||||
beam.visible = false;
|
||||
beam.color = Color(0x76e0e0ff);
|
||||
}
|
||||
|
||||
~TransferSupports() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if(beam !is null) {
|
||||
beam.markForDeletion();
|
||||
@beam = null;
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if(isTemporary)
|
||||
call(null);
|
||||
}
|
||||
|
||||
void hover(Object@ target, const vec2i& mouse) {
|
||||
beam.position = strategicPosition(obj);
|
||||
if(target !is null && target.hasLeaderAI && target.SupplyCapacity > 0)
|
||||
beam.endPosition = strategicPosition(target);
|
||||
else
|
||||
beam.endPosition = mouseToGrid(mouse);
|
||||
beam.rebuildTransform();
|
||||
beam.visible = true;
|
||||
}
|
||||
|
||||
bool valid(Object@ target) {
|
||||
if(target is null || !target.hasLeaderAI)
|
||||
return false;
|
||||
if(target.owner !is obj.owner)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
if(target !is null && target !is obj)
|
||||
openSupportOverlay(obj, target);
|
||||
}
|
||||
|
||||
string emptyMessage() {
|
||||
return locale::TRANSFER_SUPPORT_SHIPS;
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) {
|
||||
return locale::TRANSFER_SUPPORT_SHIPS;
|
||||
}
|
||||
|
||||
void draw(Object@ target, bool valid) override {
|
||||
}
|
||||
};
|
||||
|
||||
void doTransfer(bool pressed) {
|
||||
if(pressed) {
|
||||
auto@ objs = immediateSelection;
|
||||
Object@ source;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
if(objs[i].hasLeaderAI && objs[i].owner is playerEmpire) {
|
||||
@source = objs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(source !is null)
|
||||
targetObject(TransferSupports(source));
|
||||
}
|
||||
}
|
||||
|
||||
void doTransfer(Object@ source, bool isTemporary) {
|
||||
if(source !is null && source.owner is playerEmpire && source.hasLeaderAI)
|
||||
targetObject(TransferSupports(source, isTemporary));
|
||||
}
|
||||
|
||||
class ColonizePlanets : ObjectTargeting {
|
||||
array<Object@> objs;
|
||||
|
||||
ColonizePlanets(array<Object@>& sources) {
|
||||
allowMultiple = true;
|
||||
objs.reserve(sources.length);
|
||||
for(uint i = 0, cnt = sources.length; i < cnt; ++i) {
|
||||
Object@ obj = sources[i];
|
||||
if(obj.isPlanet)
|
||||
objs.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid(Object@ target) {
|
||||
if(target !is null && target.isPlanet) {
|
||||
auto@ owner = target.visibleOwner;
|
||||
if(owner is null || !owner.valid || (owner is playerEmpire && cast<Planet>(target).Population < 1.0))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
bool anyColonized = false;
|
||||
|
||||
if(target.hasResources) {
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.isPlanet && obj.owner is playerEmpire && obj.canSafelyColonize && obj !is target) {
|
||||
obj.colonize(target);
|
||||
anyColonized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(anyColonized)
|
||||
sound::order_goto.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) {
|
||||
if(valid)
|
||||
return locale::COLONIZE_GENERIC;
|
||||
else
|
||||
return locale::ONLY_PLANETS;
|
||||
}
|
||||
};
|
||||
|
||||
void doColonize(bool pressed) {
|
||||
if(pressed) {
|
||||
auto@ objs = immediateSelection;
|
||||
if(anySelected(ofType=OT_Planet, owned=true, list=objs)) {
|
||||
targetObject(ColonizePlanets(objs));
|
||||
}
|
||||
else if(anySelected(ofType=OT_Planet, list=objs)) {
|
||||
bool anyColonized = false;
|
||||
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.isPlanet && cast<Planet>(obj).Population < 1.0) {
|
||||
playerEmpire.autoColonize(obj);
|
||||
anyColonized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(anyColonized)
|
||||
sound::generic_click.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
else {
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AttackTarget : ObjectTargeting {
|
||||
array<Object@> objs;
|
||||
|
||||
AttackTarget(array<Object@>& sources) {
|
||||
objs.reserve(sources.length);
|
||||
for(uint i = 0, cnt = sources.length; i < cnt; ++i) {
|
||||
Object@ obj = sources[i];
|
||||
|
||||
if(obj.isShip && obj.owner is playerEmpire && obj.hasLeaderAI)
|
||||
objs.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid(Object@ target) {
|
||||
if(target !is null && playerEmpire.isHostile(target.owner))
|
||||
return target.isShip || target.isOrbital || target.isPlanet;
|
||||
return false;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
bool anyAttacked = false;
|
||||
|
||||
if(target.hasResources) {
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
auto@ obj = objs[i];
|
||||
if(obj.hasLeaderAI && obj.owner is playerEmpire) {
|
||||
obj.addAttackOrder(target, shiftKey);
|
||||
anyAttacked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(anyAttacked)
|
||||
sound::order_attack.play(priority=true);
|
||||
else
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
|
||||
string message(Object@ target, bool valid) {
|
||||
if(!valid)
|
||||
if(target.isShip || target.isOrbital || target.isPlanet)
|
||||
return locale::ONLY_WAR;
|
||||
else
|
||||
return locale::ONLY_ATTACKABLE;
|
||||
|
||||
return format(locale::ATTACK_TARGET, target.name);
|
||||
}
|
||||
};
|
||||
|
||||
void doAttack(bool pressed) {
|
||||
if(pressed) {
|
||||
auto@ objs = immediateSelection;
|
||||
if(anySelected(ofType=OT_Ship, owned=true, list=objs)) {
|
||||
targetObject(AttackTarget(objs));
|
||||
}
|
||||
else {
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doQuickExport(Object@ obj) {
|
||||
if(obj !is null && obj.hasResources) {
|
||||
array<Object@> objs;
|
||||
objs.insertLast(obj);
|
||||
targetObject(ExportResources(getResource(obj.primaryResourceType), objs));
|
||||
}
|
||||
}
|
||||
|
||||
void doQuickExport(Object@ obj, bool isTemporary) {
|
||||
if(obj !is null && obj.hasResources) {
|
||||
array<Object@> objs;
|
||||
objs.insertLast(obj);
|
||||
targetObject(ExportResources(getResource(obj.primaryResourceType), objs, isTemporary));
|
||||
}
|
||||
}
|
||||
|
||||
void doQuickExport(const array<Object@>& objs, bool isTemporary) {
|
||||
array<Object@> sources;
|
||||
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
||||
if(objs[i] !is null && objs[i].hasResources && objs[i].primaryResourceType != uint(-1))
|
||||
sources.insertLast(objs[i]);
|
||||
}
|
||||
if(sources.length == 1)
|
||||
targetObject(ExportResources(getResource(sources[0].primaryResourceType), sources, isTemporary));
|
||||
else if(sources.length != 0)
|
||||
targetObject(ExportResources(null, sources, isTemporary));
|
||||
}
|
||||
|
||||
void gotoHomeworld(bool pressed) {
|
||||
if(pressed) {
|
||||
Object@ hw = playerEmpire.Homeworld;
|
||||
if(hw !is null && hw.owner is playerEmpire)
|
||||
zoomTabTo(hw);
|
||||
}
|
||||
}
|
||||
|
||||
void stopOrder(bool pressed) {
|
||||
if(!pressed) {
|
||||
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i) {
|
||||
Object@ obj = selectedObjects[i];
|
||||
if(obj !is null && obj.hasLeaderAI)
|
||||
obj.clearOrders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
keybinds::Global.addBind(KB_EXPORT, "doExport");
|
||||
keybinds::Global.addBind(KB_TRANSFER_SUPPORTS, "doTransfer");
|
||||
keybinds::Global.addBind(KB_CANCEL_EXPORT, "doCancelExport");
|
||||
keybinds::Global.addBind(KB_AUTO_IMPORT, "doAutoImport");
|
||||
keybinds::Global.addBind(KB_COLONIZE, "doColonize");
|
||||
keybinds::Global.addBind(KB_ATTACK, "doAttack");
|
||||
keybinds::Global.addBind(KB_HOMEWORLD, "gotoHomeworld");
|
||||
keybinds::Global.addBind(KB_STOP, "stopOrder");
|
||||
}
|
||||
|
||||
array<Ability> cacheAbilities;
|
||||
Object@ cacheObj;
|
||||
double cacheTime = INFINITY;
|
||||
|
||||
void cacheCurrent() {
|
||||
Object@ selected = selectedObject;
|
||||
if(selected !is cacheObj || cacheTime > frameTime) {
|
||||
@cacheObj = selected;
|
||||
cacheTime = frameTime + 2.0;
|
||||
|
||||
if(cacheObj !is null && cacheObj.hasAbilities)
|
||||
cacheAbilities.syncFrom(cacheObj.getAbilities());
|
||||
else
|
||||
cacheAbilities.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool objectKeyEvent(int key, bool pressed) {
|
||||
if(pressed)
|
||||
return false;
|
||||
int modKey = modifyKey(key) & ~MASK_SHIFT;
|
||||
cacheCurrent();
|
||||
for(uint i = 0, cnt = cacheAbilities.length; i < cnt; ++i) {
|
||||
auto@ abl = cacheAbilities[i];
|
||||
if(abl.disabled)
|
||||
continue;
|
||||
if(abl.type.hotkey == modKey) {
|
||||
if(abl.type.targets.length == 0) {
|
||||
if(abl.obj !is null)
|
||||
abl.obj.activateAbility(abl.id);
|
||||
else
|
||||
abl.emp.activateAbility(abl.id);
|
||||
}
|
||||
else if(abl.type.targets[0].type == TT_Point) {
|
||||
toggleAbilityTargetPoint(abl);
|
||||
}
|
||||
else if(abl.type.targets[0].type == TT_Object) {
|
||||
toggleAbilityTargetObject(abl);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import resources;
|
||||
import obj_selection;
|
||||
import influence;
|
||||
|
||||
//class TariffVote : ConsoleCommand {
|
||||
// void execute(const string& args) {
|
||||
// InfluenceVote vote(getInfluenceVoteType("TradeTariff"));
|
||||
// @vote.subjects[0].obj = selectedObject;
|
||||
|
||||
// createInfluenceVote(vote);
|
||||
// }
|
||||
//};
|
||||
|
||||
/*class CancelVote : ConsoleCommand {*/
|
||||
/* void execute(const string& args) {*/
|
||||
/* InfluenceVote vote(getInfluenceVoteType("CancelVote"));*/
|
||||
/* vote.subjects[0].index = toInt(args);*/
|
||||
|
||||
/* createInfluenceVote(vote);*/
|
||||
/* }*/
|
||||
/*};*/
|
||||
|
||||
/*class PrintVotes : ConsoleCommand {*/
|
||||
/* void execute(const string& args) {*/
|
||||
/* InfluenceVote[] votes;*/
|
||||
/* votes.syncFrom(getActiveInfluenceVotes());*/
|
||||
|
||||
/* for(uint i = 0, cnt = votes.length; i < cnt; ++i) {*/
|
||||
/* votes[i].dump();*/
|
||||
/* print("");*/
|
||||
/* }*/
|
||||
/* }*/
|
||||
/*};*/
|
||||
|
||||
class PrintInfluence : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
print("Influence: "+playerEmpire.Influence);
|
||||
}
|
||||
};
|
||||
|
||||
class OrderSupport : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
if(selectedObject is null || !selectedObject.hasLeaderAI) {
|
||||
error("Error: Need to have valid group leader selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
int space = args.findFirst(" ");
|
||||
if(args.length == 0 || space == -1) {
|
||||
print("Usage: order_support <amount> <design name>");
|
||||
return;
|
||||
}
|
||||
|
||||
string strAmt = args.substr(0, space);
|
||||
string strDesign = args.substr(space+1, args.length - space - 1);
|
||||
|
||||
const Design@ dsg = playerEmpire.getDesign(strDesign);
|
||||
if(dsg is null) {
|
||||
error("ERROR: Cannot find design with name "+strDesign);
|
||||
return;
|
||||
}
|
||||
|
||||
selectedObject.orderSupports(dsg, toUInt(strAmt));
|
||||
}
|
||||
};
|
||||
|
||||
import void setFleetPlanesShown(bool enabled) from "nodes.FleetPlane";
|
||||
import void setFleetIconsShown(bool enabled) from "nodes.FleetPlane";
|
||||
import void setPlanetPlanesShown(bool enabled) from "nodes.PlanetIcon";
|
||||
import void setPlanetIconsShown(bool enabled) from "nodes.PlanetIcon";
|
||||
import void setStrategicIconsShown(bool enabled) from "nodes.StrategicIcon";
|
||||
import void setSystemPlanesShown(bool enabled) from "nodes.SystemPlane";
|
||||
import void setGalaxyPlanesShown(bool enabled) from "nodes.GalaxyPlane";
|
||||
import void setTerritoryBordersShown(bool enabled) from "nodes.Territory";
|
||||
import void setTradeLinesShown(bool enabled) from "nodes.TradeLines";
|
||||
|
||||
class CineMode : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
bool mode = args.length == 0 || toBool(args);
|
||||
render3DIcons = !mode;
|
||||
setFleetPlanesShown(!mode);
|
||||
setFleetIconsShown(!mode);
|
||||
setPlanetPlanesShown(!mode);
|
||||
setPlanetIconsShown(!mode);
|
||||
setStrategicIconsShown(!mode);
|
||||
setSystemPlanesShown(!mode);
|
||||
setTerritoryBordersShown(!mode);
|
||||
setTradeLinesShown(!mode);
|
||||
}
|
||||
};
|
||||
|
||||
class GalaxyPlane : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
bool mode = args.length == 0 || toBool(args);
|
||||
setGalaxyPlanesShown(mode);
|
||||
}
|
||||
};
|
||||
|
||||
class HideGUI : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
hide_ui = args.length == 0 || toBool(args);
|
||||
}
|
||||
};
|
||||
|
||||
class KickCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
auto@ pl = getPlayers();
|
||||
for(uint i = 0, cnt = pl.length; i < cnt; ++i) {
|
||||
if(pl[i].name.equals_nocase(args))
|
||||
mpKick(pl[i].id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PasswordCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
mpSetPassword(args);
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
//addConsoleCommand("tariff_vote", TariffVote());
|
||||
addConsoleCommand("order_support", OrderSupport());
|
||||
/*addConsoleCommand("cancel_vote", CancelVote());*/
|
||||
/*addConsoleCommand("print_votes", PrintVotes());*/
|
||||
addConsoleCommand("print_influence", PrintInfluence());
|
||||
addConsoleCommand("cine_mode", CineMode());
|
||||
addConsoleCommand("galaxy_plane", GalaxyPlane());
|
||||
addConsoleCommand("hide_gui", HideGUI());
|
||||
addConsoleCommand("kick", KickCommand());
|
||||
addConsoleCommand("password", PasswordCommand());
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
Mutex gfxLock;
|
||||
array<GfxEffect@> gfxNodes;
|
||||
|
||||
class GfxEffect {
|
||||
int64 id;
|
||||
double expireTime = INFINITY;
|
||||
|
||||
void init() {}
|
||||
void destroy() {}
|
||||
void render(double time) {}
|
||||
};
|
||||
|
||||
class BeamEffect : GfxEffect {
|
||||
Object@ from;
|
||||
Object@ to;
|
||||
Color color;
|
||||
double width;
|
||||
const Material@ mat = material::MoveBeam;
|
||||
|
||||
BeamNode@ node;
|
||||
|
||||
void init() override {
|
||||
@node = BeamNode(mat, width, from.node_position, to.node_position, false);
|
||||
node.color = color;
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
if(node !is null) {
|
||||
node.visible = false;
|
||||
node.markForDeletion();
|
||||
@node = null;
|
||||
}
|
||||
}
|
||||
|
||||
void render(double time) override {
|
||||
if(node !is null) {
|
||||
node.position = from.node_position;
|
||||
node.endPosition = to.node_position;
|
||||
node.visible = from.visible || to.visible;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PersistentParticlesEffect : GfxEffect {
|
||||
Object@ from;
|
||||
string particleName;
|
||||
double size = 1.0;
|
||||
PersistentGfx@ node;
|
||||
|
||||
void init() override {
|
||||
@node = PersistentGfx();
|
||||
node.establish(from, particleName, size);
|
||||
node.setObjectRotation(true);
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
if(node !is null) {
|
||||
node.stop();
|
||||
@node = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void makeBeamEffect(int64 id, Object@ from, Object@ to, uint color, double width, string material = "", double timer = -1.0) {
|
||||
if(from is null || to is null)
|
||||
return;
|
||||
|
||||
BeamEffect eff;
|
||||
eff.id = id;
|
||||
@eff.from = from;
|
||||
@eff.to = to;
|
||||
eff.color.color = color;
|
||||
eff.width = width;
|
||||
|
||||
if(material.length != 0)
|
||||
@eff.mat = getMaterial(material);
|
||||
|
||||
if(timer > 0)
|
||||
eff.expireTime = gameTime + timer;
|
||||
|
||||
addGfxEffect(eff);
|
||||
}
|
||||
|
||||
void makePersistentParticles(int64 id, Object@ from, string particleName, double size = 1.0, double timer = -1.0) {
|
||||
if(from is null)
|
||||
return;
|
||||
|
||||
PersistentParticlesEffect eff;
|
||||
eff.id = id;
|
||||
@eff.from = from;
|
||||
eff.size = size;
|
||||
eff.particleName = particleName;
|
||||
|
||||
if(timer > 0)
|
||||
eff.expireTime = gameTime + timer;
|
||||
|
||||
addGfxEffect(eff);
|
||||
}
|
||||
|
||||
void addGfxEffect(GfxEffect@ eff) {
|
||||
Lock lck(gfxLock);
|
||||
|
||||
bool found = false;
|
||||
for(uint i = 0, cnt = gfxNodes.length; i < cnt; ++i) {
|
||||
if(gfxNodes[i].id == eff.id) {
|
||||
gfxNodes[i].destroy();
|
||||
@gfxNodes[i] = eff;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
gfxNodes.insertLast(eff);
|
||||
eff.init();
|
||||
}
|
||||
|
||||
void removeGfxEffect(int64 id) {
|
||||
Lock lck(gfxLock);
|
||||
for(uint i = 0, cnt = gfxNodes.length; i < cnt; ++i) {
|
||||
if(gfxNodes[i].id == id) {
|
||||
gfxNodes[i].destroy();
|
||||
gfxNodes.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
double gtime = gameTime;
|
||||
Lock lck(gfxLock);
|
||||
for(int i = gfxNodes.length - 1; i >= 0; --i) {
|
||||
if(gfxNodes[i].expireTime < gtime) {
|
||||
gfxNodes[i].destroy();
|
||||
gfxNodes.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render(double time) {
|
||||
Lock lck(gfxLock);
|
||||
for(uint i = 0, cnt = gfxNodes.length; i < cnt; ++i)
|
||||
gfxNodes[i].render(time);
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
//input.as
|
||||
//--------
|
||||
//Various script callbacks for global input handling.
|
||||
//Also handles passing events to the GUI
|
||||
|
||||
#priority init 1
|
||||
import elements.IGuiElement;
|
||||
import targeting.targeting;
|
||||
import navigation.SmartCamera;
|
||||
import util.convar;
|
||||
from navigation.elevation import getElevationIntersect;
|
||||
|
||||
import bool onMouseEvent(const MouseEvent&) from "gui";
|
||||
import bool onKeyboardEvent(const KeyboardEvent&) from "gui";
|
||||
import bool onGuiEvent(const GuiEvent&) from "gui";
|
||||
import void onGuiNavigate(vec2d direction) from "gui";
|
||||
import IGuiElement@ getGuiFocus() from "gui";
|
||||
import bool isGuiHovered() from "gui";
|
||||
import void selectionClick(uint button, bool pressed) from "obj_selection";
|
||||
import void dragSelect(const recti&) from "obj_selection";
|
||||
import Object@ get_hoveredObject() from "obj_selection";
|
||||
import Object@ get_selectedObject() from "obj_selection";
|
||||
import Object@ get_uiObject() from "obj_selection";
|
||||
import void updateHoveredObject() from "obj_selection";
|
||||
import bool objectKeyEvent(int key, bool pressed) from "commands";
|
||||
import void switchToTab(int pos) from "tabs.tabbar";
|
||||
import bool tabEscape() from "tabs.tabbar";
|
||||
import double getAverageElevation() from "navigation.elevation";
|
||||
|
||||
ConVar FollowAI("follow_ai", 0);
|
||||
|
||||
//TODO: Anything directly using the engine camera should go through the camera module
|
||||
|
||||
const double tickZoom = 1.2;
|
||||
const double zoomPerPixel = 0.01;
|
||||
const double camAnglesPerPixel = 0.22 * twopi / 360.0;
|
||||
const double movePerPixel = 0.001;
|
||||
const double zoomPerSecond = 0.45;
|
||||
const double rollBorder = 0.4;
|
||||
const double camMotionPerSecond = 80.0;
|
||||
|
||||
vec2i dragArea, dragFrom;
|
||||
array<double> edgePanDelay = {0.0, 0.0, 0.0, 0.0};
|
||||
array<double> edgePanRampup = {0.3, 0.3, 0.3, 0.3};
|
||||
array<double> edgePanTimers = {0.0, 0.0, 0.0, 0.0};
|
||||
bool doingBoxSelect = false;
|
||||
|
||||
Joystick joystick(1);
|
||||
bool joystickConnected = false;
|
||||
bool joystickActive = false;
|
||||
|
||||
SmartCamera@ ActiveCamera;
|
||||
SmartCamera PreviousCamera;
|
||||
|
||||
SmartCamera@ get_activeCamera() {
|
||||
return ActiveCamera;
|
||||
}
|
||||
|
||||
SmartCamera@ get_lastCamera() {
|
||||
if(ActiveCamera !is null)
|
||||
return ActiveCamera;
|
||||
return PreviousCamera;
|
||||
}
|
||||
|
||||
void set_activeCamera(SmartCamera@ cam) {
|
||||
if(ActiveCamera !is null)
|
||||
PreviousCamera = ActiveCamera;
|
||||
|
||||
@ActiveCamera = cam;
|
||||
}
|
||||
|
||||
void copyPreviousCamera(SmartCamera@ cam) {
|
||||
if(ActiveCamera !is null)
|
||||
cam = ActiveCamera;
|
||||
else
|
||||
cam = PreviousCamera;
|
||||
}
|
||||
|
||||
vec3d mouseToGrid(vec2i mpos) {
|
||||
double plane = getAverageElevation();
|
||||
vec3d dest;
|
||||
line3dd ray = ActiveCamera.screenToRay(mpos);
|
||||
if(!getElevationIntersect(ray, dest))
|
||||
ray.intersectY(dest, plane, false);
|
||||
return dest;
|
||||
}
|
||||
|
||||
vec3d mouseToGrid(vec2i mpos, double plane) {
|
||||
vec3d dest;
|
||||
line3dd ray = ActiveCamera.screenToRay(mpos);
|
||||
if(!getElevationIntersect(ray, dest))
|
||||
ray.intersectY(dest, plane, false);
|
||||
return dest;
|
||||
}
|
||||
|
||||
void take_screenshot(bool pressed) {
|
||||
if(!pressed)
|
||||
takeScreenshot("screenshot");
|
||||
}
|
||||
|
||||
//Called when the platform overlay (e.g. Steam Overlay) is opened or closed
|
||||
double prevGameSpeed = 1.0;
|
||||
void onOverlayChanged(bool overlayOpen) {
|
||||
if(!mpServer && !mpClient) {
|
||||
if(overlayOpen) {
|
||||
prevGameSpeed = gameSpeed;
|
||||
gameSpeed = 0.0;
|
||||
}
|
||||
else {
|
||||
gameSpeed = prevGameSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Called whenever the mouse wheel is used on the 3D view.
|
||||
// x: Amount scrolled horizontally.
|
||||
// y: Amount scrolled vertically.
|
||||
void onMouseWheel(double x, double y) {
|
||||
if(isGuiHovered() || ActiveCamera is null) {
|
||||
MouseEvent evt;
|
||||
evt.type = MET_Scrolled;
|
||||
evt.x = int(floor(x));
|
||||
evt.y = int(floor(y));
|
||||
onMouseEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if(targetMouseWheel(x, y))
|
||||
return;
|
||||
|
||||
activeCamera.zoom(y);
|
||||
}
|
||||
|
||||
//Called when the mouse is dragged with one or more buttons
|
||||
//pressed on the 3D view.
|
||||
// buttons: Bitmask of pressed buttons (0x1 << button_num)
|
||||
// x: Mouse x position.
|
||||
// y: Mouse y position.
|
||||
// dx: Movement in the x dimension since last event.
|
||||
// dy: Movement in the y dimension since last event.
|
||||
void onMouseDragged(int buttons, int x, int y, int dx, int dy) {
|
||||
if(activeCamera is null)
|
||||
return;
|
||||
if(targetMouseDragged(buttons, x, y, dx, dy))
|
||||
return;
|
||||
|
||||
Camera@ cam = activeCamera.camera;
|
||||
vec2i screen = screenSize;
|
||||
|
||||
if(buttons == 0x1) {
|
||||
doingBoxSelect = true;
|
||||
dragArea += vec2i(dx,-dy);
|
||||
dragFrom = vec2i(x,y);
|
||||
}
|
||||
else if(buttons == 0x2) {
|
||||
double dist = sqrt(sqr(double(x)/double(screen.x) - 0.5)
|
||||
+ sqr(double(y)/double(screen.y) - 0.5));
|
||||
|
||||
if(settings::bEdgeRoll && rollBorder < dist) {
|
||||
if(x < screen.x / 2)
|
||||
activeCamera.roll(dy);
|
||||
else
|
||||
activeCamera.roll(-dy);
|
||||
}
|
||||
else {
|
||||
activeCamera.rotate(dx, dy);
|
||||
}
|
||||
}
|
||||
else if(buttons == 0x3) {
|
||||
//-- Left and right buttons
|
||||
if(settings::bInvertHorizRot)
|
||||
dx = -dx;
|
||||
|
||||
//Horizontal movement does an absolute yaw
|
||||
if(cam.inverted)
|
||||
cam.abs_yaw(double(-dx) * camAnglesPerPixel);
|
||||
else
|
||||
cam.abs_yaw(double(dx) * camAnglesPerPixel);
|
||||
|
||||
//Vertical movement zooms
|
||||
cam.zoom(1.0 + zoomPerPixel * double(-dy));
|
||||
CAM_ZOOMED = true;
|
||||
}
|
||||
else if(buttons == 0x4) {
|
||||
//-- Only middle button
|
||||
//Pan the world
|
||||
activeCamera.pan(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
//Called when a drag event thas ended
|
||||
bool onMouseDragEnd(int button) {
|
||||
if(targetMouseDragEnd(button))
|
||||
return true;
|
||||
if(doingBoxSelect) {
|
||||
if(button != 0) {
|
||||
doingBoxSelect = false;
|
||||
return false;
|
||||
}
|
||||
recti box = recti_area(dragFrom, dragArea);
|
||||
|
||||
//Make sure the rect is oriented top-down, left-right
|
||||
if(box.topLeft.x > box.botRight.x) {
|
||||
int temp = box.topLeft.x;
|
||||
box.topLeft.x = box.botRight.x;
|
||||
box.botRight.x = temp;
|
||||
}
|
||||
if(box.topLeft.y > box.botRight.y) {
|
||||
int temp = box.topLeft.y;
|
||||
box.topLeft.y = box.botRight.y;
|
||||
box.botRight.y = temp;
|
||||
}
|
||||
|
||||
dragSelect(box);
|
||||
|
||||
//Place the cursor to the dragged destination
|
||||
doingBoxSelect = false;
|
||||
mousePos = dragFrom + dragArea;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Called when the mouse is moved over the 3d view without
|
||||
//any buttons being held.
|
||||
// x: Mouse x position.
|
||||
// y: Mouse y position.
|
||||
void onMouseMoved(int x, int y) {
|
||||
MouseEvent evt;
|
||||
evt.type = MET_Moved;
|
||||
evt.x = x;
|
||||
evt.y = y;
|
||||
if(onMouseEvent(evt))
|
||||
return;
|
||||
if(targetMouseMoved(x, y))
|
||||
return;
|
||||
}
|
||||
|
||||
//Called when a mouse button is clicked
|
||||
// buttons: Button that was clicked.
|
||||
//void onMouseClicked(int button) {
|
||||
//}
|
||||
|
||||
//Called when a mouse button is double-clicked
|
||||
// buttons: Button that was clicked.
|
||||
//void onMouseDoubleClicked(int button) {
|
||||
//}
|
||||
|
||||
//General handler for when a mouse button event occurs
|
||||
// button: Button that the event pertains to.
|
||||
// pressed: Whether the button was pressed or released.
|
||||
bool onMouseButton(int button, bool pressed) {
|
||||
MouseEvent evt;
|
||||
if(pressed)
|
||||
evt.type = MET_Button_Down;
|
||||
else
|
||||
evt.type = MET_Button_Up;
|
||||
evt.button = button;
|
||||
vec2i mpos = mousePos;
|
||||
evt.x = mpos.x;
|
||||
evt.y = mpos.y;
|
||||
|
||||
bool fromTarget = uiObject !is null;
|
||||
if(fromTarget && targetMouseButton(button, pressed))
|
||||
return false;
|
||||
if(onMouseEvent(evt))
|
||||
return true;
|
||||
if(!fromTarget && targetMouseButton(button, pressed))
|
||||
return false;
|
||||
if(pressed)
|
||||
dragArea = vec2i(0,0);
|
||||
selectionClick(button, pressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Called when a unicode char is typed into the main 3D view.
|
||||
// chr: Unicode code point that was typed.
|
||||
bool onCharTyped(int chr) {
|
||||
KeyboardEvent evt;
|
||||
evt.type = KET_Key_Typed;
|
||||
evt.key = chr;
|
||||
return onKeyboardEvent(evt);
|
||||
}
|
||||
|
||||
//Called when a key event occurs when the 3D view has focus.
|
||||
// key: Keyboard key that was pressed.
|
||||
// pressed: Whether the key was pressed or released.
|
||||
bool onKeyEvent(int key, int keyaction) {
|
||||
KeyboardEvent evt;
|
||||
bool pressed = (keyaction & KA_Pressed) != 0;
|
||||
if(pressed)
|
||||
evt.type = KET_Key_Down;
|
||||
else
|
||||
evt.type = KET_Key_Up;
|
||||
evt.key = key;
|
||||
if(onKeyboardEvent(evt))
|
||||
return true;
|
||||
if(objectKeyEvent(key, pressed))
|
||||
return true;
|
||||
if(targetKeyEvent(key, pressed))
|
||||
return true;
|
||||
if(pressed && key == KEY_ESC) {
|
||||
if(tabEscape())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint[] worldMove = {0, 0, 0, 0};
|
||||
void world_forward(bool pressed) {
|
||||
worldMove[3] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void world_left(bool pressed) {
|
||||
worldMove[0] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void world_right(bool pressed) {
|
||||
worldMove[2] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void world_backward(bool pressed) {
|
||||
worldMove[1] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void reset_cam(bool pressed) {
|
||||
if(!pressed && activeCamera !is null) {
|
||||
Camera@ cam = activeCamera.camera;
|
||||
activeCamera.reset();
|
||||
Object@ hw = playerEmpire.Homeworld;
|
||||
if(hw is null)
|
||||
@hw = selectedObject;
|
||||
if(hw !is null)
|
||||
activeCamera.zoomTo(hw);
|
||||
else
|
||||
activeCamera.zoomTo(vec3d());
|
||||
cam.snap();
|
||||
}
|
||||
}
|
||||
|
||||
uint[] zoom = {0, 0};
|
||||
void zoom_in(bool pressed) {
|
||||
zoom[0] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void zoom_out(bool pressed) {
|
||||
zoom[1] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
void zoom_object(bool pressed) {
|
||||
if(!pressed && ActiveCamera !is null) {
|
||||
Object@ sel = selectedObject;
|
||||
if(sel !is null && sel.visible)
|
||||
ActiveCamera.zoomLockObject(sel);
|
||||
}
|
||||
}
|
||||
|
||||
double threshold(double x, double thresh) {
|
||||
if(abs(x) < thresh)
|
||||
return 0.0;
|
||||
else
|
||||
return x;
|
||||
}
|
||||
|
||||
bool leftCannon = false;
|
||||
bool movePressed = false;
|
||||
|
||||
void onControllerButton(int i, bool pressed) {
|
||||
joystickActive = true;
|
||||
if(pressed) {
|
||||
if(i == GP_LB)
|
||||
switchToTab(-1);
|
||||
else if(i == GP_RB)
|
||||
switchToTab(+1);
|
||||
}
|
||||
}
|
||||
|
||||
GuiEvent joyGuiEvent;
|
||||
|
||||
vec3d lastAIFocus;
|
||||
|
||||
void tick(double time) {
|
||||
if(game_state != GS_Game)
|
||||
return;
|
||||
|
||||
if(FollowAI.value != 0) {
|
||||
vec3d to = playerEmpire.aiFocus;
|
||||
if(to != lastAIFocus) {
|
||||
activeCamera.zoomTo(to);
|
||||
lastAIFocus = to;
|
||||
}
|
||||
}
|
||||
|
||||
double tickMotion = time * camMotionPerSecond * 60.0;
|
||||
|
||||
//Polling a disconnected joystick on Windows takes ~10ms, so avoid it
|
||||
if(joystickConnected && joystick.poll()) {
|
||||
//Send buttons to gui
|
||||
for(uint i = 0, cnt = joystick.buttonCount; i < cnt; ++i) {
|
||||
uint8 state = joystick.button[i];
|
||||
if(state == JBS_Pressed) {
|
||||
//GUI Event
|
||||
joyGuiEvent.type = GUI_Controller_Down;
|
||||
joyGuiEvent.value = i;
|
||||
@joyGuiEvent.caller = getGuiFocus();
|
||||
if(!onGuiEvent(joyGuiEvent))
|
||||
onControllerButton(i, true);
|
||||
}
|
||||
else if(state == JBS_Released) {
|
||||
//GUI Event
|
||||
joyGuiEvent.type = GUI_Controller_Up;
|
||||
joyGuiEvent.value = i;
|
||||
@joyGuiEvent.caller = getGuiFocus();
|
||||
if(!onGuiEvent(joyGuiEvent))
|
||||
onControllerButton(i, false);
|
||||
}
|
||||
}
|
||||
|
||||
if(activeCamera !is null) {
|
||||
//Panning with left analog
|
||||
vec2d pan = vec2d(-joystick.axis[GP_AXIS_LX], joystick.axis[GP_AXIS_LY]);
|
||||
if(pan.length > 0.05) {
|
||||
pan.normalize(tickMotion * 0.7);
|
||||
activeCamera.pan(pan);
|
||||
mousePos = screenSize * 0.5;
|
||||
joystickActive = true;
|
||||
}
|
||||
|
||||
//Zoom and rotate with right analog
|
||||
double zoom = -joystick.axis[GP_AXIS_RY];
|
||||
if(abs(zoom) > 0)
|
||||
activeCamera.camera.zoom(1.0 + 7.0 * zoomPerSecond * zoom * time * settings::dZoomSpeed);
|
||||
|
||||
double yaw = -joystick.axis[GP_AXIS_RX];
|
||||
if(abs(yaw) > 0)
|
||||
activeCamera.rotate(600.0 * yaw * time, 0);
|
||||
}
|
||||
else {
|
||||
//Navigate gui with left stick
|
||||
vec2d dir = vec2d(joystick.axis[GP_AXIS_LX], -joystick.axis[GP_AXIS_LY]);
|
||||
if(dir.length > 0.25) {
|
||||
if(!movePressed)
|
||||
onGuiNavigate(dir);
|
||||
movePressed = true;
|
||||
}
|
||||
else {
|
||||
movePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*for(uint i = 0; i < joystick.buttonCount; ++i) {
|
||||
uint8 state = joystick.button[i];
|
||||
if(state == JBS_Pressed)
|
||||
print("Joystick 1, key " + i + " pressed");
|
||||
else if(state == JBS_Released)
|
||||
print("Joystick 1, key " + i + " released");
|
||||
}
|
||||
|
||||
for(uint i = 0; i < joystick.axisCount; ++i) {
|
||||
double axis = joystick.axis[i];
|
||||
if(abs(axis) > 0.01)
|
||||
print("Joystick axis " + i + ": " + axis);
|
||||
}*/
|
||||
|
||||
if(joystickActive)
|
||||
updateHoveredObject();
|
||||
}
|
||||
|
||||
if(activeCamera is null)
|
||||
return;
|
||||
|
||||
Camera@ cam = activeCamera.camera;
|
||||
bool inverted = cam.inverted;
|
||||
|
||||
vec2d move;
|
||||
|
||||
//Handle edge panning
|
||||
vec2i mouse = mousePos;
|
||||
if(settings::bEdgePan && windowFocused && mouseOverWindow) {
|
||||
vec2i screen = screenSize;
|
||||
|
||||
if(mouse.x <= 0) {
|
||||
edgePanTimers[0] += time;
|
||||
if(edgePanTimers[0] >= edgePanDelay[0]) {
|
||||
move.x += 1;
|
||||
if(edgePanRampup[0] > 0)
|
||||
tickMotion *= clamp((edgePanTimers[0] - edgePanDelay[0]) / edgePanRampup[0], 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
edgePanTimers[0] = 0.0;
|
||||
}
|
||||
|
||||
if(mouse.x >= screen.x - 2) {
|
||||
edgePanTimers[1] += time;
|
||||
if(edgePanTimers[1] >= edgePanDelay[1]) {
|
||||
move.x -= 1;
|
||||
if(edgePanRampup[1] > 0)
|
||||
tickMotion *= clamp((edgePanTimers[1] - edgePanDelay[1]) / edgePanRampup[1], 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
edgePanTimers[1] = 0.0;
|
||||
}
|
||||
|
||||
if(mouse.y <= 0) {
|
||||
edgePanTimers[2] += time;
|
||||
if(edgePanTimers[2] >= edgePanDelay[2]) {
|
||||
move.y += 1;
|
||||
if(edgePanRampup[2] > 0)
|
||||
tickMotion *= clamp((edgePanTimers[2] - edgePanDelay[2]) / edgePanRampup[2], 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
edgePanTimers[2] = 0.0;
|
||||
}
|
||||
|
||||
if(mouse.y >= screen.y - 2) {
|
||||
edgePanTimers[3] += time;
|
||||
if(edgePanTimers[3] >= edgePanDelay[3]) {
|
||||
move.y -= 1;
|
||||
if(edgePanRampup[0] > 0)
|
||||
tickMotion *= clamp((edgePanTimers[3] - edgePanDelay[3]) / edgePanRampup[3], 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
edgePanTimers[3] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
//Move the world in the direction the keys were pressed
|
||||
if(worldMove[0] ^ worldMove[2] != 0) {
|
||||
if(worldMove[0] != 0)
|
||||
move.x += 1;
|
||||
else
|
||||
move.x += -1;
|
||||
}
|
||||
if(worldMove[1] ^ worldMove[3] != 0) {
|
||||
if(worldMove[1] != 0)
|
||||
move.y += -1;
|
||||
else
|
||||
move.y += 1;
|
||||
}
|
||||
|
||||
if(move.x != 0 || move.y != 0) {
|
||||
move.normalize(tickMotion);
|
||||
activeCamera.pan(move);
|
||||
}
|
||||
|
||||
//Zoom the camera appropriately
|
||||
if(zoom[0] ^ zoom[1] != 0) {
|
||||
if(zoom[0] != 0)
|
||||
cam.zoom(1.0 - zoomPerSecond * time * settings::dZoomSpeed);
|
||||
else
|
||||
cam.zoom(1.0 + zoomPerSecond * time * settings::dZoomSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
keybinds::Global.addBind(KB_WORLD_FORWARD, "world_forward");
|
||||
keybinds::Global.addBind(KB_WORLD_LEFT, "world_left");
|
||||
keybinds::Global.addBind(KB_WORLD_RIGHT, "world_right");
|
||||
keybinds::Global.addBind(KB_WORLD_BACKWARD, "world_backward");
|
||||
|
||||
keybinds::Global.addBind(KB_ZOOM_IN, "zoom_in");
|
||||
keybinds::Global.addBind(KB_ZOOM_OUT, "zoom_out");
|
||||
|
||||
keybinds::Global.addBind(KB_RESET_CAM, "reset_cam");
|
||||
keybinds::Global.addBind(KB_TAKE_SCREENSHOT, "take_screenshot");
|
||||
|
||||
keybinds::Global.addBind(KB_ZOOM_LOCK_OBJECT, "zoom_object");
|
||||
|
||||
//joystickConnected = joystick.connected();
|
||||
PreviousCamera.reset();
|
||||
|
||||
if(settings::bDelayTopEdge) {
|
||||
edgePanDelay[2] = 0.3;
|
||||
edgePanRampup[2] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(doingBoxSelect)
|
||||
drawRectangle(recti_area(dragFrom, dragArea), Color(0x00ff0040));
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
const float fadeInTime = 1000.f;
|
||||
const float fadeOutTime = 3000.f;
|
||||
|
||||
//Delay (seconds) to avoid playing music after the track ends
|
||||
float silenceTime = 0;
|
||||
|
||||
//Calculated volume for the music tracks
|
||||
double musicVolume = 1.0;
|
||||
|
||||
class Layer {
|
||||
Movement@ movement;
|
||||
Sound@ track;
|
||||
float currentVolume = 1;
|
||||
float volumeGoal = 1;
|
||||
float fadeTime = 0;
|
||||
|
||||
string name;
|
||||
|
||||
Layer(Movement@ moveTrack, const string& file, float Volume = 1.f, bool loop = false) {
|
||||
name = file;
|
||||
@movement = moveTrack;
|
||||
|
||||
//Sounds don't play if the volume is 0
|
||||
if(soundVolume <= 0)
|
||||
return;
|
||||
|
||||
@track = playTrack(file, loop, true);
|
||||
currentVolume = Volume;
|
||||
volumeGoal = Volume;
|
||||
if(track !is null) {
|
||||
track.volume = Volume;
|
||||
}
|
||||
else {
|
||||
error("Could not load track " + file);
|
||||
}
|
||||
}
|
||||
|
||||
void volumeFade(float goal, float seconds) {
|
||||
volumeGoal = goal;
|
||||
fadeTime = seconds;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if(track !is null)
|
||||
track.stop();
|
||||
@track = null;
|
||||
@movement = null;
|
||||
}
|
||||
|
||||
void play() {
|
||||
if(track !is null)
|
||||
track.paused = false;
|
||||
}
|
||||
|
||||
void updateVolume() {
|
||||
track.volume = currentVolume * musicVolume;
|
||||
}
|
||||
|
||||
bool update(double time) {
|
||||
if(track is null)
|
||||
return true;
|
||||
|
||||
if(currentVolume != volumeGoal) {
|
||||
if(time >= fadeTime) {
|
||||
currentVolume = volumeGoal;
|
||||
fadeTime = 0;
|
||||
}
|
||||
else {
|
||||
currentVolume += (volumeGoal - currentVolume) * (time/fadeTime);
|
||||
fadeTime -= time;
|
||||
}
|
||||
|
||||
track.volume = currentVolume * musicVolume;
|
||||
}
|
||||
|
||||
if(track.playing) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
@track = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
array<string> tracks;
|
||||
int lastTrack = -1;
|
||||
string get_nextTrack() {
|
||||
int nextIndex;
|
||||
do {
|
||||
nextIndex = randomi(0,tracks.length-1);
|
||||
} while(nextIndex == lastTrack && tracks.length != 1);
|
||||
|
||||
lastTrack = nextIndex;
|
||||
return tracks[nextIndex];
|
||||
}
|
||||
|
||||
Namespace musicVars;
|
||||
array<Arrangement@> arrangements, queued;
|
||||
array<Layer@> layers;
|
||||
StatTracker winTracker(stat::ShipsDestroyed, "ships_destroyed"), lossTracker(stat::ShipsLost, "ships_lost");
|
||||
|
||||
class Movement {
|
||||
string name;
|
||||
string track;
|
||||
bool loop = false;
|
||||
Formula@ volume;
|
||||
|
||||
int volVar, timeVar, playingVar;
|
||||
|
||||
Movement(const string& Name) {
|
||||
name = Name;
|
||||
volVar = musicVars.lookup(name + ".volume", true);
|
||||
timeVar = musicVars.lookup(name + ".time", true);
|
||||
playingVar = musicVars.lookup(name + ".playing", true);
|
||||
}
|
||||
};
|
||||
|
||||
class Arrangement {
|
||||
array<Movement@> movements;
|
||||
double playOrder = 0;
|
||||
string name;
|
||||
|
||||
int opCmp(const Arrangement& other) {
|
||||
if(playOrder < other.playOrder)
|
||||
return -1;
|
||||
else if(playOrder == other.playOrder)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
void prepare() {
|
||||
//Load up all tracks, then start them when they've all been loaded
|
||||
for(uint i = 0; i < movements.length; ++i) {
|
||||
Movement@ movement = movements[i];
|
||||
layers.insertLast(Layer(movement, movement.track, 0, movement.loop));
|
||||
musicVars.setConstant(movement.playingVar, 1.0);
|
||||
}
|
||||
|
||||
for(uint i = 0; i < layers.length; ++i)
|
||||
layers[i].play();
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
if(!soundEnabled)
|
||||
return;
|
||||
|
||||
//Load arrangements
|
||||
ReadFile file(resolve("data/music/orchestra.txt"));
|
||||
|
||||
Arrangement@ arrange;
|
||||
Movement@ movement;
|
||||
|
||||
string key, value;
|
||||
while(file++) {
|
||||
key = file.key;
|
||||
value = file.value;
|
||||
|
||||
if(key == "Arrangement") {
|
||||
@arrange = Arrangement();
|
||||
arrange.name = value;
|
||||
arrangements.insertLast(arrange);
|
||||
}
|
||||
else if(arrange !is null) {
|
||||
if(key == "Era") {
|
||||
if(value == "Industrial")
|
||||
arrange.playOrder = randomd(0.75, 1.25);
|
||||
else if(value == "Imperial")
|
||||
arrange.playOrder = randomd(1.2, 1.5);
|
||||
else
|
||||
arrange.playOrder = randomd(0.0, 1.0);
|
||||
}
|
||||
else if(key == "Movement") {
|
||||
@movement = Movement(value);
|
||||
arrange.movements.insertLast(movement);
|
||||
}
|
||||
else if(movement !is null) {
|
||||
if(key == "Track") {
|
||||
movement.track = value;
|
||||
}
|
||||
else if(key == "Volume") {
|
||||
@movement.volume = Formula(value);
|
||||
}
|
||||
else if(key == "Loop") {
|
||||
movement.loop = toBool(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we're starting a new-ish game, play the music in a semi-ordered way
|
||||
if(gameTime < 30.0 * 60.0) {
|
||||
queued = arrangements;
|
||||
queued.sortAsc();
|
||||
uint remove = min(uint(max(double(queued.length) * gameTime / (30.0 / 60.0), 0.0)), queued.length);
|
||||
for(uint i = 0; i < remove; ++i)
|
||||
queued.removeAt(0);
|
||||
}
|
||||
|
||||
if(soundVolume > 0)
|
||||
playNewArrangement();
|
||||
}
|
||||
|
||||
Arrangement@ prevArrangement;
|
||||
void randomize() {
|
||||
queued = arrangements;
|
||||
for(uint i = 0, cnt = queued.length; i < cnt; ++i) {
|
||||
uint o = randomi(i, cnt-1);
|
||||
if(o != i) {
|
||||
auto@ a = queued[i];
|
||||
@queued[i] = queued[o];
|
||||
@queued[o] = a;
|
||||
}
|
||||
}
|
||||
|
||||
if(prevArrangement !is null && prevArrangement is queued[0] && queued.length > 1) {
|
||||
uint i = randomi(1, queued.length-1);
|
||||
auto@ a = queued[0];
|
||||
@queued[0] = queued[i];
|
||||
@queued[i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
void playNewArrangement() {
|
||||
if(arrangements.length == 0)
|
||||
return;
|
||||
else if(arrangements.length == 1) {
|
||||
arrangements[0].prepare();
|
||||
return;
|
||||
}
|
||||
else if(queued.length == 0) {
|
||||
randomize();
|
||||
}
|
||||
|
||||
Arrangement@ nextArrangement = queued[0];
|
||||
queued.removeAt(0);
|
||||
nextArrangement.prepare();
|
||||
@prevArrangement = nextArrangement;
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
for(uint i = 0; i < layers.length; ++i)
|
||||
layers[i].stop();
|
||||
}
|
||||
|
||||
double prevMusicVol = 1.0, prevMasterVol = 1.0, prevSFXVol = 1.0;
|
||||
void tick(double time) {
|
||||
if(!soundEnabled)
|
||||
return;
|
||||
|
||||
//Update volume settings
|
||||
if(prevMusicVol != settings::dMusicVolume || prevSFXVol != settings::dSFXVolume || prevMasterVol != settings::dMasterVolume) {
|
||||
if(settings::dSFXVolume < 0.01)
|
||||
musicVolume = settings::dMusicVolume;
|
||||
else
|
||||
musicVolume = settings::dMusicVolume / settings::dSFXVolume;
|
||||
|
||||
for(int i = layers.length - 1; i >= 0; --i)
|
||||
layers[i].updateVolume();
|
||||
prevMusicVol = settings::dMusicVolume;
|
||||
prevMasterVol = settings::dMasterVolume;
|
||||
prevSFXVol = settings::dSFXVolume;
|
||||
}
|
||||
|
||||
if(layers.length == 0) {
|
||||
silenceTime -= time;
|
||||
|
||||
if(soundVolume > 0 && silenceTime <= 0.0) {
|
||||
playNewArrangement();
|
||||
//Silence music for 3 seconds after we attempt to resume the tracks
|
||||
// Mostly, this avoids spamming errors when failing to play trakcs for some reason
|
||||
silenceTime = 3.0;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
winTracker.update();
|
||||
lossTracker.update();
|
||||
|
||||
bool activeTrack = false;
|
||||
|
||||
for(int i = layers.length - 1; i >= 0; --i) {
|
||||
Layer@ layer = layers[i];
|
||||
if(layer.update(time)) {
|
||||
musicVars.setConstant(layer.movement.playingVar, 0.0);
|
||||
layers.removeAt(i);
|
||||
}
|
||||
else {
|
||||
if(!layer.movement.loop || layer.currentVolume > 0.01)
|
||||
activeTrack = true;
|
||||
musicVars.setConstant(layer.movement.playingVar, 1.0);
|
||||
musicVars.setConstant(layer.movement.timeVar, double(layer.track.playPosition) / 1000.0);
|
||||
musicVars.setConstant(layer.movement.volVar, layer.currentVolume);
|
||||
layer.volumeFade(layer.movement.volume.evaluate(musicVars), 2);
|
||||
}
|
||||
}
|
||||
|
||||
if(!activeTrack) {
|
||||
for(int i = layers.length - 1; i >= 0; --i)
|
||||
layers[i].stop();
|
||||
layers.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class StatTracker {
|
||||
stat::EmpireStat stat;
|
||||
|
||||
string name;
|
||||
int accVar;
|
||||
|
||||
uint lastTime = 0;
|
||||
int lastVal = 0;
|
||||
int accumulation = 0;
|
||||
|
||||
float decay = 0.7;
|
||||
|
||||
StatTracker(stat::EmpireStat Stat, string Name) {
|
||||
stat = Stat;
|
||||
name = Name;
|
||||
accVar = musicVars.lookup(name + ".acc", true);
|
||||
}
|
||||
|
||||
void update() {
|
||||
if(lastTime + 1 >= (systemTime/1000))
|
||||
return;
|
||||
lastTime = systemTime/1000;
|
||||
|
||||
if(accumulation > 0)
|
||||
accumulation = int(ceil(float(accumulation) * decay)) - 1;
|
||||
|
||||
int value = 0;
|
||||
{
|
||||
StatHistory history(playerEmpire, stat);
|
||||
if(history.advance(-1))
|
||||
value = history.intVal;
|
||||
}
|
||||
|
||||
//Accumulate events
|
||||
accumulation = max(accumulation + (value - lastVal),0);
|
||||
|
||||
lastVal = value;
|
||||
|
||||
musicVars.setConstant(accVar, double(accumulation));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
//Draws a blackhole and its accretion disk
|
||||
final class BlackholeNodeScript {
|
||||
BlackholeNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.customColor = true;
|
||||
node.autoCull = false;
|
||||
}
|
||||
|
||||
void establish(Node& node, Star& star) {
|
||||
auto@ gfx = PersistentGfx();
|
||||
gfx.establish(star, "BlackholeGlitter");
|
||||
gfx.rotate(quaterniond_fromAxisAngle(vec3d_right(), pi * 0.5));
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
return isSphereVisible(node.abs_position, 20.0 * node.abs_scale);
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
double dist = node.sortDistance / (2500.0 * node.abs_scale * pixelSizeRatio);
|
||||
|
||||
if(dist < 1.0) {
|
||||
node.applyTransform();
|
||||
|
||||
material::Blackhole.switchTo();
|
||||
model::Sphere_max.draw(node.sortDistance / (node.abs_scale * pixelSizeRatio));
|
||||
|
||||
undoTransform();
|
||||
|
||||
renderPlane(material::AccretionDisk, node.abs_position, node.abs_scale * 20.0, Color(0xffffffff));
|
||||
}
|
||||
|
||||
if(dist > 0.5) {
|
||||
Color col(node.color);
|
||||
col.a = dist > 1.0 ? 255 : int((dist - 0.5)*255.0/0.5);
|
||||
renderBillboard(material::DistantStar, node.abs_position, node.abs_scale * 320.0, 0.0, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
const double MAX_SIZE = 4000.0;
|
||||
const double APPROACH_EPSILON = 0.0002;
|
||||
|
||||
bool SHOW_FLEET_PLANES = true;
|
||||
bool SHOW_FLEET_ICONS = true;
|
||||
|
||||
void setFleetPlanesShown(bool enabled) {
|
||||
SHOW_FLEET_PLANES = enabled;
|
||||
}
|
||||
|
||||
bool getFleetPlanesShown() {
|
||||
return SHOW_FLEET_PLANES;
|
||||
}
|
||||
|
||||
void setFleetIconsShown(bool enabled) {
|
||||
SHOW_FLEET_ICONS = enabled;
|
||||
}
|
||||
|
||||
bool getFleetIconsShown() {
|
||||
return SHOW_FLEET_ICONS;
|
||||
}
|
||||
|
||||
const Color FTLColor(0x00c0ff80);
|
||||
const Color CombatColor(0xffc60080);
|
||||
vec4f CombatVec;
|
||||
vec4f FTLVec;
|
||||
|
||||
void init() {
|
||||
CombatColor.toVec4(CombatVec);
|
||||
FTLColor.toVec4(FTLVec);
|
||||
}
|
||||
|
||||
class FleetPlaneNodeScript {
|
||||
Object@ leader;
|
||||
Sprite fleetIcon;
|
||||
double radius;
|
||||
bool hasPlane = false;
|
||||
bool withFleet = false;
|
||||
bool rotate = true;
|
||||
bool wasVisible = false;
|
||||
|
||||
FleetPlaneNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.visible = false;
|
||||
node.needsTransform = false;
|
||||
node.fixedSize = true;
|
||||
node.createPhysics();
|
||||
}
|
||||
|
||||
void establish(Node& node, Object& obj, double rad) {
|
||||
@leader = obj;
|
||||
radius = rad;
|
||||
|
||||
if(obj.isShip) {
|
||||
const Design@ dsg = cast<Ship>(obj).blueprint.design;
|
||||
if(dsg !is null)
|
||||
fleetIcon = dsg.fleetIcon;
|
||||
}
|
||||
|
||||
node.scale = radius;
|
||||
node.position = obj.position;
|
||||
@node.object = obj;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void set_hasSupply(bool supply) {
|
||||
hasPlane = supply;
|
||||
}
|
||||
|
||||
void set_hasFleet(bool has) {
|
||||
withFleet = has;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(node.visible && leader !is null) {
|
||||
node.position = leader.node_position;
|
||||
|
||||
double size = leader.radius;
|
||||
size = 0.2 * (1.0 + size) / (3.0 + size);
|
||||
if(wasVisible)
|
||||
size *= node.sortDistance * 0.25;
|
||||
else
|
||||
size *= cameraPos.distanceTo(node.position) * 0.25;
|
||||
size = min(size, MAX_SIZE);
|
||||
if(leader.selected)
|
||||
size *= 1.1;
|
||||
node.scale = size * 0.5;
|
||||
rotate = !leader.hasOrbit;
|
||||
|
||||
node.rebuildTransform();
|
||||
wasVisible = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
wasVisible = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(hasPlane && node.sortDistance < 2000.0 && node.sortDistance >= 500.0 && SHOW_FLEET_PLANES) {
|
||||
Color color(0xffffff14);
|
||||
if(node.sortDistance < 600.0)
|
||||
color.a = double(color.a) * (node.sortDistance - 500.0) / 100.0;
|
||||
renderPlane(material::FleetCircle, node.abs_position, radius, color);
|
||||
}
|
||||
|
||||
double iconDist = 600.0 * leader.radius;
|
||||
|
||||
if(node.sortDistance > iconDist && SHOW_FLEET_ICONS) {
|
||||
//TODO: Use leader's node instead
|
||||
|
||||
vec3d camFacing = cameraFacing, camUp = cameraUp;
|
||||
double rot = 0.0;
|
||||
|
||||
if(rotate) {
|
||||
vec3d objFacing = leader.node_rotation * vec3d_front();
|
||||
double alongDot = camFacing.dot(objFacing);
|
||||
|
||||
if(alongDot > -0.9999 && alongDot < 0.9999) {
|
||||
objFacing -= camFacing * alongDot;
|
||||
objFacing.normalize();
|
||||
|
||||
vec3d camRight = camFacing.cross(camUp).normalized();
|
||||
rot = acos(camRight.dot(objFacing));
|
||||
if(camRight.cross(camFacing).dot(objFacing) < 0)
|
||||
rot = -rot;
|
||||
}
|
||||
else {
|
||||
rot = alongDot > 0 ? pi * 0.5 : pi * -0.5;
|
||||
}
|
||||
}
|
||||
|
||||
Empire@ owner = leader.owner;
|
||||
Color col = owner.color;
|
||||
if(node.sortDistance < iconDist * 2.0)
|
||||
col.a = uint8(255.0 * (node.sortDistance - iconDist) / iconDist);
|
||||
node.color = col;
|
||||
|
||||
Ship@ ship = cast<Ship>(leader);
|
||||
if(ship !is null && ship.isFTLing)
|
||||
shader::GLOW_COLOR = FTLVec;
|
||||
else if(owner is playerEmpire && leader.inCombat)
|
||||
shader::GLOW_COLOR = CombatVec;
|
||||
else
|
||||
shader::GLOW_COLOR.w = 0.f;
|
||||
|
||||
shader::APPROACH = APPROACH_EPSILON;
|
||||
if(fleetIcon.valid)
|
||||
renderBillboard(fleetIcon.sheet, fleetIcon.index, node.abs_position, node.scale * 2.0, rot);
|
||||
else
|
||||
renderBillboard(spritesheet::ShipGroupIcons, 0, node.abs_position, node.scale * 2.0, rot);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import util.convar;
|
||||
|
||||
final class GasSprite {
|
||||
bool draw = true;
|
||||
uint baseCol = 0xffffff00;
|
||||
int trueAlpha = 20;
|
||||
int baseAlpha = 0;
|
||||
Color resultCol;
|
||||
double scale = 1.0;
|
||||
double rotation = randomd(0,twopi);
|
||||
double sortDist = 0;
|
||||
uint index;
|
||||
vec3d position;
|
||||
|
||||
GasSprite(const vec3d& pos, double Scale, uint col, bool structured, int baseAlpha = 0) {
|
||||
position = pos;
|
||||
scale = Scale * 2.0;
|
||||
baseCol = col & 0xffffff00;
|
||||
trueAlpha = int(col & 0xff);
|
||||
index = structured ? randomi(0,23) : randomi(24,47);
|
||||
this.baseAlpha = baseAlpha;
|
||||
}
|
||||
};
|
||||
|
||||
final class GalaxyGasScript {
|
||||
array<GasSprite@> sprites;
|
||||
array<int64> sorter;
|
||||
|
||||
bool dirty = true;
|
||||
|
||||
GalaxyGasScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.autoCull = false;
|
||||
}
|
||||
|
||||
void addSprite(vec3d pos, double scale, uint color, bool structured, int baseAlpha = 0) {
|
||||
sorter.insertLast( int64(sprites.length) );
|
||||
sprites.insertLast( GasSprite(pos, scale, color, structured, baseAlpha) );
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void recalculateBounds(Node& node) {
|
||||
vec3d p;
|
||||
for(uint i = 0, cnt = sprites.length; i < cnt; ++i)
|
||||
p += sprites[i].position;
|
||||
p /= double(sprites.length);
|
||||
node.position = p;
|
||||
double maxDist = 0;
|
||||
for(uint i = 0, cnt = sprites.length; i < cnt; ++i)
|
||||
maxDist = max(maxDist, p.distanceTo(sprites[i].position) + sprites[i].scale);
|
||||
node.scale = maxDist;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(!settings::bShowGalaxyGas)
|
||||
return false;
|
||||
if(dirty) {
|
||||
dirty = false;
|
||||
if(sprites.length == 0)
|
||||
return false;
|
||||
recalculateBounds(node);
|
||||
}
|
||||
if(!node.isInView())
|
||||
return false;
|
||||
bool anyVisible = false;
|
||||
|
||||
//Update the sprite's color and decide if it needs rendered/sorted
|
||||
for(uint i = 0, cnt = sprites.length; i < cnt; ++i) {
|
||||
uint index = uint(sorter[i]) & 0x7fffffff;
|
||||
GasSprite@ sprite = sprites[index];
|
||||
|
||||
double sortDist = getCameraDistance(sprite.position);
|
||||
if(sortDist < 0.0) {
|
||||
sprite.draw = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
double fadeOut = ((sortDist / 30000.0) - 0.3);
|
||||
int trueAlpha = sprite.trueAlpha;
|
||||
int a = fadeOut < 1.0 ? int(double(trueAlpha) * fadeOut) : trueAlpha;
|
||||
a += sprite.baseAlpha;
|
||||
if(a < 4) {
|
||||
sprite.draw = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite.resultCol = Color(sprite.baseCol | a);
|
||||
sprite.draw = true;
|
||||
anyVisible = true;
|
||||
|
||||
sorter[i] = (int64(sortDist * 100.0) << 32) | int64(index);
|
||||
}
|
||||
|
||||
if(anyVisible)
|
||||
sorter.sortAsc();
|
||||
|
||||
return anyVisible;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
for(uint i = 0, cnt = sprites.length; i < cnt; ++i) {
|
||||
GasSprite@ sprite = sprites[sorter[i] & 0x7fffffff];
|
||||
if(sprite.draw)
|
||||
renderBillboard(spritesheet::Nebulas, sprite.index, sprite.position, sprite.scale, sprite.rotation, sprite.resultCol);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
export setGalaxyPlanesShown;
|
||||
export getGalaxyPlanesShown;
|
||||
|
||||
bool SHOW_GALAXY_PLANES = false;
|
||||
void setGalaxyPlanesShown(bool enabled) {
|
||||
SHOW_GALAXY_PLANES = enabled;
|
||||
}
|
||||
|
||||
bool getGalaxyPlanesShown() {
|
||||
return SHOW_GALAXY_PLANES;
|
||||
}
|
||||
|
||||
class GalaxyPlaneScript {
|
||||
vec3d origin;
|
||||
double radius;
|
||||
double planeDist = 0;
|
||||
|
||||
GalaxyPlaneScript(Node& node) {
|
||||
}
|
||||
|
||||
void establish(Node& node, vec3d Origin, double Radius) {
|
||||
origin = Origin;
|
||||
radius = Radius;
|
||||
|
||||
node.scale = radius;
|
||||
node.position = origin;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(!SHOW_GALAXY_PLANES)
|
||||
return false;
|
||||
|
||||
//Calculate distance to plane
|
||||
line3dd camLine(cameraPos, cameraPos+cameraFacing);
|
||||
vec3d intersect;
|
||||
if(!camLine.intersectY(intersect, origin.y, false)) {
|
||||
intersect = cameraPos;
|
||||
intersect.y = origin.y;
|
||||
planeDist = sqrt(
|
||||
sqr(max(0.0, intersect.distanceTo(origin) - radius))
|
||||
+ sqr(cameraPos.y - origin.y));
|
||||
}
|
||||
else {
|
||||
planeDist = intersect.distanceTo(cameraPos);
|
||||
max(0.0, intersect.distanceTo(origin) - radius);
|
||||
}
|
||||
|
||||
return planeDist > 3000.0;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
shader::RADIUS = radius;
|
||||
shader::PLANE_DISTANCE = planeDist;
|
||||
|
||||
drawPolygonStart(PT_Quads, 1, material::GalaxyPlane);
|
||||
drawPolygonPoint(origin + vec3d(-radius, 0, -radius), vec2f(0.f, 0.f));
|
||||
drawPolygonPoint(origin + vec3d(+radius, 0, -radius), vec2f(1.f, 0.f));
|
||||
drawPolygonPoint(origin + vec3d(+radius, 0, +radius), vec2f(1.f, 1.f));
|
||||
drawPolygonPoint(origin + vec3d(-radius, 0, +radius), vec2f(0.f, 1.f));
|
||||
drawPolygonEnd();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
//Draws an expanding, darkening nova
|
||||
final class NovaNodeScript {
|
||||
double life = 0, duration = 900.0;
|
||||
double lastTick = gameTime;
|
||||
double alpha = 1.0;
|
||||
|
||||
NovaNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.customColor = true;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
double t = gameTime;
|
||||
if(t > lastTick) {
|
||||
double time = t - lastTick;
|
||||
lastTick = t;
|
||||
|
||||
life += time;
|
||||
if(life > duration) {
|
||||
node.markForDeletion();
|
||||
return false;
|
||||
}
|
||||
|
||||
alpha = 1.0 - (life / duration);
|
||||
|
||||
node.scale = 1000.0 + 1000.0 * life / duration;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
node.applyTransform();
|
||||
|
||||
shader::NOVA_AGE = life / duration;
|
||||
|
||||
material::Nova.switchTo();
|
||||
model::Sphere_max.draw(node.sortDistance / (node.abs_scale * pixelSizeRatio));
|
||||
|
||||
undoTransform();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import orbitals;
|
||||
from nodes.FleetPlane import SHOW_FLEET_PLANES;
|
||||
|
||||
const double MAX_SIZE = 500.0;
|
||||
|
||||
final class OrbitalNodeScript {
|
||||
const Orbital@ obj;
|
||||
const OrbitalModule@ def;
|
||||
double fleetPlane = 0.0;
|
||||
|
||||
OrbitalNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.memorable = true;
|
||||
}
|
||||
|
||||
void establish(Node& node, Orbital& orbital, uint type) {
|
||||
@obj = orbital;
|
||||
@def = getOrbitalModule(type);
|
||||
}
|
||||
|
||||
void setFleetPlane(double radius) {
|
||||
fleetPlane = radius;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(def is null)
|
||||
return;
|
||||
|
||||
if(fleetPlane != 0 && node.sortDistance < 2000.0 && node.sortDistance >= 150.0 && SHOW_FLEET_PLANES) {
|
||||
Color color(0xffffff14);
|
||||
if(node.sortDistance < 250.0)
|
||||
color.a = double(color.a) * (node.sortDistance - 150.0) / 100.0;
|
||||
renderPlane(material::FleetCircle, node.abs_position, fleetPlane, color);
|
||||
}
|
||||
|
||||
node.applyTransform();
|
||||
|
||||
def.material.switchTo();
|
||||
def.model.draw();
|
||||
|
||||
/*if(obj.Shield > 0) {*/
|
||||
/* shader::SHIELD_STRENGTH = float(obj.Shield / obj.MaxShield);*/
|
||||
/* material::Shield.switchTo();*/
|
||||
/* def.model.draw();*/
|
||||
/*}*/
|
||||
|
||||
undoTransform();
|
||||
|
||||
if(node.sortDistance > 600.0 && def.distantIcon.valid) {
|
||||
double size = obj.radius * def.iconSize;
|
||||
size *= node.sortDistance * 0.09;
|
||||
size = min(size, MAX_SIZE);
|
||||
if(obj.selected)
|
||||
size *= 1.1;
|
||||
|
||||
Empire@ owner = obj.owner;
|
||||
Color col;
|
||||
if(owner !is null)
|
||||
col = owner.color;
|
||||
renderBillboard(def.distantIcon.sheet, def.distantIcon.index, node.abs_position, size, 0.0, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
class PersistentGfxScript {
|
||||
Object@ obj;
|
||||
ParticleSystemNode@ gfx;
|
||||
Region@ prevParent;
|
||||
bool objRotate = false;
|
||||
|
||||
PersistentGfxScript(Node& node) {
|
||||
node.visible = false;
|
||||
}
|
||||
|
||||
~PersistentGfxScript() {
|
||||
if(gfx !is null) {
|
||||
gfx.stop();
|
||||
@gfx = null;
|
||||
}
|
||||
}
|
||||
|
||||
void stop(Node& node) {
|
||||
if(gfx !is null) {
|
||||
gfx.stop();
|
||||
@gfx = null;
|
||||
}
|
||||
|
||||
@obj = null;
|
||||
@prevParent = null;
|
||||
node.markForDeletion();
|
||||
}
|
||||
|
||||
void establish(Node& node, Object@ Obj, string psName, double scale) {
|
||||
@obj = Obj;
|
||||
@gfx = playParticleSystem(psName, obj.position, obj.radius * scale);
|
||||
@prevParent = obj.region;
|
||||
gfx.hintParentObject(prevParent, false);
|
||||
node.visible = obj !is null;
|
||||
gfx.visible = obj.visible || obj.known;
|
||||
}
|
||||
|
||||
void establish(Node& node, vec3d at, string psName, double scale, Object@ parent) {
|
||||
@gfx = playParticleSystem(psName, at, scale);
|
||||
gfx.hintParentObject(parent, false);
|
||||
node.visible = true;
|
||||
}
|
||||
|
||||
void rotate(quaterniond rot) {
|
||||
if(gfx !is null)
|
||||
gfx.emitRot = rot;
|
||||
}
|
||||
|
||||
void setObjectRotation(bool value) {
|
||||
objRotate = value;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(gfx is null)
|
||||
return false;
|
||||
|
||||
if(obj !is null) {
|
||||
if(!obj.valid) {
|
||||
stop(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(obj.region !is prevParent) {
|
||||
@prevParent = obj.region;
|
||||
gfx.hintParentObject(prevParent, false);
|
||||
}
|
||||
|
||||
//TODO: Make less awful
|
||||
bool visible = obj.visible || obj.known;
|
||||
gfx.visible = visible;
|
||||
|
||||
gfx.position = obj.position;
|
||||
if(objRotate)
|
||||
gfx.emitRot = obj.rotation;
|
||||
gfx.rebuildTransform();
|
||||
|
||||
gfx.velocity = obj.velocity;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//We don't actually need to render anything ourselves, we just manage a particle effect
|
||||
//void render(Node& node) {
|
||||
//}
|
||||
};
|
||||
@@ -0,0 +1,375 @@
|
||||
import resources;
|
||||
import planet_types;
|
||||
import util.convar;
|
||||
import nodes.StrategicIcon;
|
||||
import planet_levels;
|
||||
|
||||
const double APPROACH_EPSILON = 0.002;
|
||||
const double OUTSIDE_DISTANCE = 12000.0;
|
||||
const double OUTSIDE_SIZE_MAX = 25000.0;
|
||||
const double ANIMATE_TIME = 0.45;
|
||||
const double GRAVITY_DISC_MAX_DIST = 1000.0;
|
||||
|
||||
const double FADE_DIST_MIN = 250;
|
||||
const double FADE_DIST_MAX = 300;
|
||||
const double VERY_DISTANT_START = 56000.0;
|
||||
const double VERY_DISTANT_END = 80000.0;
|
||||
|
||||
vec4f DISABLE_NORMAL, DISABLE_POPULATION;
|
||||
|
||||
ConVar PlanetIconSize("planet_icon_size", 0.0225);
|
||||
ConVar PlanetResourceSize("planet_resource_size", 0.4);
|
||||
ConVar PlanetTypeSize("planet_type_size", 0.7);
|
||||
ConVar PlanetSelectedSize("planet_selected_size", 1.0);
|
||||
const ResourceClass@ foodClass;
|
||||
const ResourceClass@ scalableClass;
|
||||
|
||||
void init() {
|
||||
@foodClass = getResourceClass("Food");
|
||||
@scalableClass = getResourceClass("Scalable");
|
||||
|
||||
colors::Red.toVec4(DISABLE_NORMAL);
|
||||
Color(0xff6300ff).toVec4(DISABLE_POPULATION);
|
||||
}
|
||||
|
||||
bool SHOW_PLANET_PLANES = true;
|
||||
bool SHOW_PLANET_ICONS = true;
|
||||
|
||||
void setPlanetPlanesShown(bool enabled) {
|
||||
SHOW_PLANET_PLANES = enabled;
|
||||
}
|
||||
|
||||
bool getPlanetPlanesShown() {
|
||||
return SHOW_PLANET_PLANES;
|
||||
}
|
||||
|
||||
void setPlanetIconsShown(bool enabled) {
|
||||
SHOW_PLANET_ICONS = enabled;
|
||||
}
|
||||
|
||||
bool getPlanetIconsShown() {
|
||||
return SHOW_PLANET_ICONS;
|
||||
}
|
||||
|
||||
final class PlanetIconNodeScript : StrategicIcon {
|
||||
Planet@ pl;
|
||||
uint level;
|
||||
uint typeIcon;
|
||||
uint resourceIcon = uint(-1);
|
||||
ResourceSheet@ rs;
|
||||
const Material@ levelMat, flag;
|
||||
vec4f disableColor;
|
||||
Color color;
|
||||
const ResourceType@ type;
|
||||
|
||||
Empire@ captureEmp;
|
||||
float capturePct = 0.f;
|
||||
|
||||
float resourceClass = -1.f;
|
||||
bool plane = false;
|
||||
bool isPlayer = false;
|
||||
bool isDisabled = true;
|
||||
bool isExported = false;
|
||||
bool isMaterial = false;
|
||||
bool isDecaying = false;
|
||||
bool isBeingColonized = false;
|
||||
bool isMemory = false;
|
||||
bool isLowPop = false;
|
||||
|
||||
PlanetIconNodeScript(Node& node) {
|
||||
node.visible = false;
|
||||
super(node);
|
||||
}
|
||||
|
||||
void establish(Node& node, Planet& planet) {
|
||||
@pl = planet;
|
||||
typeIcon = getPlanetType(planet.PlanetType).distantIcon.index;
|
||||
@node.object = planet;
|
||||
node.position = planet.position;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void setOwner(Empire@ owner) {
|
||||
if(owner !is null && owner.valid) {
|
||||
color = owner.color;
|
||||
@flag = owner.flag;
|
||||
isPlayer = true;
|
||||
}
|
||||
else {
|
||||
color = Color(0xb0b0b0ff);
|
||||
isPlayer = false;
|
||||
}
|
||||
}
|
||||
|
||||
void setLevel(uint lvl) {
|
||||
level = lvl;
|
||||
if(rs !is null) {
|
||||
@levelMat = rs.getMaterial(level);
|
||||
}
|
||||
else switch(level) {
|
||||
case 0: @levelMat = material::PlanetLevel0; break;
|
||||
case 1: @levelMat = material::PlanetLevel1; break;
|
||||
case 2: @levelMat = material::PlanetLevel2; break;
|
||||
case 3: @levelMat = material::PlanetLevel3; break;
|
||||
case 4: @levelMat = material::PlanetLevel4; break;
|
||||
case 5: default: @levelMat = material::PlanetLevel5; break;
|
||||
}
|
||||
}
|
||||
|
||||
void setResource(uint id) {
|
||||
@type = getResource(id);
|
||||
if(type is null) {
|
||||
resourceIcon = uint(-1);
|
||||
resourceClass = -1.f;
|
||||
}
|
||||
else {
|
||||
@rs = type.distantSheet;
|
||||
resourceIcon = type.smallIcon.index;
|
||||
if(type.level > 0 && type.level <= 3)
|
||||
resourceClass = 3.f + float(type.level);
|
||||
else if(type.cls is foodClass)
|
||||
resourceClass = 7.f;
|
||||
else if(type.cls is scalableClass)
|
||||
resourceClass = 16.f;
|
||||
else
|
||||
resourceClass = -1.f;
|
||||
}
|
||||
setLevel(level);
|
||||
}
|
||||
|
||||
void setState(bool dis, bool exp, bool mat, bool decay) {
|
||||
isDisabled = dis;
|
||||
isExported = exp;
|
||||
isMaterial = mat;
|
||||
isDecaying = decay;
|
||||
}
|
||||
|
||||
void setBeingColonized(bool value) {
|
||||
isBeingColonized = value;
|
||||
}
|
||||
|
||||
void setCapture(Empire@ emp, float pct) {
|
||||
@captureEmp = emp;
|
||||
capturePct = pct;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(!SHOW_PLANET_ICONS && !SHOW_PLANET_PLANES)
|
||||
return false;
|
||||
if(pl is null)
|
||||
return false;
|
||||
|
||||
isMemory = false;
|
||||
|
||||
{
|
||||
if(pl.visible) {
|
||||
node.visible = true;
|
||||
}
|
||||
else if(pl.known) {
|
||||
isMemory = true;
|
||||
node.visible = true;
|
||||
}
|
||||
else if(!node.visible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isMemory) {
|
||||
Empire@ owner = pl.owner;
|
||||
if(owner !is null && owner.valid) {
|
||||
color = owner.color;
|
||||
isPlayer = true;
|
||||
}
|
||||
else {
|
||||
color = Color(0xb0b0b0ff);
|
||||
isPlayer = false;
|
||||
}
|
||||
}
|
||||
|
||||
double dist = node.sortDistance * config::GFX_DISTANCE_MOD;
|
||||
plane = dist < GRAVITY_DISC_MAX_DIST;
|
||||
|
||||
vec3d plPos = pl.node_position;
|
||||
StrategicIcon::update(node, plPos, PlanetIconSize.value,
|
||||
OUTSIDE_DISTANCE, OUTSIDE_SIZE_MAX, ANIMATE_TIME,
|
||||
FADE_DIST_MIN * 10, FADE_DIST_MAX * 10);
|
||||
|
||||
if(pl.owner is playerEmpire) {
|
||||
if(isDisabled) {
|
||||
isLowPop = false;
|
||||
if(type is null) {
|
||||
disableColor = DISABLE_NORMAL;
|
||||
}
|
||||
else {
|
||||
if(pl.resourceLevel >= type.level &&
|
||||
pl.population < getPlanetLevelRequiredPop(pl, type.level)) {
|
||||
disableColor = DISABLE_POPULATION;
|
||||
}
|
||||
else {
|
||||
disableColor = DISABLE_NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
isLowPop = pl.resourceLevel > pl.level;
|
||||
disableColor = DISABLE_POPULATION;
|
||||
}
|
||||
}
|
||||
|
||||
if(isMemory)
|
||||
alpha *= 0.7f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
Empire@ owner = pl.owner;
|
||||
Color col = color;
|
||||
col.a = alpha * 255;
|
||||
|
||||
double dist = node.sortDistance * config::GFX_DISTANCE_MOD;
|
||||
float iconFade = 1.0;
|
||||
if(dist > VERY_DISTANT_START) {
|
||||
iconFade = float(clamp(1.0 - ((dist - VERY_DISTANT_START) / (VERY_DISTANT_END - VERY_DISTANT_START)), 0.0, 1.0));
|
||||
|
||||
//Distant planet icons are very simple and more numerous, so we check for that first
|
||||
if(iconFade < 0.01) {
|
||||
if(pl.selected) {
|
||||
Color scol(0xffffffff);
|
||||
scol.a = col.a;
|
||||
|
||||
renderBillboard(material::DistantPlanetSelected, node.abs_position,
|
||||
node.abs_scale * 2.0 * PlanetSelectedSize.value, 0, scol);
|
||||
shader::APPROACH += APPROACH_EPSILON;
|
||||
}
|
||||
renderBillboard(material::FadedPlanet, node.abs_position, node.abs_scale * 2.0, 0, col);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Capturing stuff
|
||||
if(captureEmp !is null && !isMemory) {
|
||||
captureEmp.color.toVec4(shader::CAPTURE_COLOR);
|
||||
shader::CAPTURE_PROGRESS = capturePct;
|
||||
}
|
||||
else if(pl.Population < 1.0 && !isMemory) {
|
||||
shader::CAPTURE_PROGRESS = pl.Population;
|
||||
owner.color.toVec4(shader::CAPTURE_COLOR);
|
||||
}
|
||||
else if(isDecaying) {
|
||||
shader::CAPTURE_PROGRESS = pl.decayTime / (config::LEVEL_DECAY_TIMER / owner.PlanetDecaySpeed);
|
||||
float pct = abs((frameTime % 1.0) - 0.5f) * 2.f;
|
||||
colors::Red.interpolate(colors::Orange, pct).toVec4(shader::CAPTURE_COLOR);
|
||||
}
|
||||
else {
|
||||
shader::CAPTURE_COLOR = vec4f();
|
||||
}
|
||||
|
||||
double orbitCircleFadeIn = FADE_DIST_MAX * 10;
|
||||
|
||||
//Draw the orbit plane
|
||||
if(dist < orbitCircleFadeIn && dist > 100.0 && SHOW_PLANET_PLANES) {
|
||||
double orbitSize = pl.OrbitSize;
|
||||
shader::CIRCLE_MIN = (pl.radius + 0.5f) / orbitSize;
|
||||
shader::CIRCLE_MAX = 1.f;
|
||||
Color orbitColor(0xffffff05);
|
||||
|
||||
|
||||
double orbitCircleFadeOut = FADE_DIST_MIN * 10;
|
||||
|
||||
double a = 0.025;
|
||||
if(pl.selected)
|
||||
a = 0.035;
|
||||
else if(dist < 250.0)
|
||||
a *= (dist - 150.0) / 100.0;
|
||||
else if(dist > orbitCircleFadeOut)
|
||||
a *= 1.0 - (dist - orbitCircleFadeOut) / (orbitCircleFadeIn - orbitCircleFadeOut);
|
||||
|
||||
if(a <= 0.0)
|
||||
orbitColor.a = 0;
|
||||
else if(a >= 1.0)
|
||||
orbitColor.a = 255;
|
||||
else
|
||||
orbitColor.a = uint8(a * 255.0);
|
||||
|
||||
vec3d center = node.abs_position;
|
||||
if(orbitColor.a > 0)
|
||||
renderPlane(material::OrbitCircle, center, orbitSize, orbitColor);
|
||||
|
||||
float prevA = shader::CAPTURE_COLOR.w;
|
||||
Color flagColor = color;
|
||||
if(isPlayer && dist > 150.0 && dist < 1850.0) {
|
||||
//NOTE: The node can be setup before the empire has a flag specified
|
||||
if(flag is null)
|
||||
@flag = owner.flag;
|
||||
flagColor.a = min(a * (6.0 * 255.0) * (1.0 - (dist - 150.0) / 1700.0), 255.0);
|
||||
if(prevA > 0.f)
|
||||
shader::CAPTURE_COLOR.w = float(flagColor.a) / 255.f * 1.5f;
|
||||
}
|
||||
else {
|
||||
flagColor.a = 0;
|
||||
shader::CAPTURE_COLOR.w = 0.f;
|
||||
}
|
||||
|
||||
if(flagColor.a > 4) {
|
||||
center.y += 0.1;
|
||||
shader::CAPTURE_COLOR.w = float(flagColor.a) / 255.f;
|
||||
|
||||
renderPlane(flag, center + vec3d(orbitSize * -0.9, 0.0, 0.0), orbitSize * 0.1, flagColor, pi * 0.5);
|
||||
renderPlane(flag, center + vec3d(orbitSize * 0.9, 0.0, 0.0), orbitSize * 0.1, flagColor, pi * 1.5);
|
||||
renderPlane(flag, center + vec3d(0.0, 0.0, orbitSize * 0.9), orbitSize * 0.1, flagColor, 0.0);
|
||||
renderPlane(flag, center + vec3d(0.0, 0.0, orbitSize * -0.9), orbitSize * 0.1, flagColor, pi);
|
||||
}
|
||||
shader::CAPTURE_COLOR.w = prevA;
|
||||
}
|
||||
|
||||
if(col.a > 2 && SHOW_PLANET_ICONS) {
|
||||
//Draw selected border
|
||||
shader::APPROACH = APPROACH_EPSILON * (col.a != 255 ? -8.0 : 1.0);
|
||||
if(pl.selected) {
|
||||
Color scol(0xffffffff);
|
||||
scol.a = col.a;
|
||||
|
||||
renderBillboard(material::DistantPlanetSelected, node.abs_position,
|
||||
node.abs_scale * 2.0 * PlanetSelectedSize.value, 0, scol);
|
||||
shader::APPROACH += APPROACH_EPSILON;
|
||||
}
|
||||
|
||||
shader::DISTANT_SPRITE_FADE = iconFade;
|
||||
|
||||
spritesheet::DistantPlanetType.getSourceUV(typeIcon, shader::DISTANT_SPRITE1);
|
||||
|
||||
if(rs !is null)
|
||||
rs.getUV(resourceIcon, shader::DISTANT_SPRITE2);
|
||||
else if(resourceIcon < 0xffffffff)
|
||||
spritesheet::ResourceIconsSmall.getSourceUV(resourceIcon, shader::DISTANT_SPRITE2);
|
||||
else
|
||||
shader::DISTANT_SPRITE2 = vec4f();
|
||||
|
||||
//Draw planet level icon
|
||||
shader::DISTANT_SPRITE_SCALE1 = PlanetTypeSize.value;
|
||||
shader::DISTANT_SPRITE_SCALE2 = PlanetResourceSize.value;
|
||||
shader::IS_COLONIZING = isBeingColonized ? 1.f : 0.f;
|
||||
shader::IS_OWNED = (isPlayer && levelMat !is null) ? 1.f : 0.f;
|
||||
shader::IS_EXPORTED = isExported ? 1.f : 0.f;
|
||||
shader::IS_DECAYING = isDecaying ? 1.f : 0.f;
|
||||
shader::RESOURCE_CLASS = resourceClass;
|
||||
|
||||
if(owner is playerEmpire) {
|
||||
shader::IS_DISABLED = (isDisabled || isLowPop) ? 1.f : 0.f;
|
||||
shader::IS_USED = (isMaterial || isDisabled || isDecaying || isLowPop) ? 1.f : 0.f;
|
||||
if(isDisabled || isLowPop)
|
||||
shader::DISABLE_COLOR = disableColor;
|
||||
}
|
||||
else {
|
||||
shader::IS_DISABLED = 0.f;
|
||||
shader::IS_USED = 1.f;
|
||||
}
|
||||
|
||||
if(levelMat is null)
|
||||
renderBillboard(material::PlanetLevel0, node.abs_position, node.abs_scale * 2.0, 0, col);
|
||||
else
|
||||
renderBillboard(levelMat, node.abs_position, node.abs_scale * 2.0, 0, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,346 @@
|
||||
import planet_types;
|
||||
|
||||
from planets.PlanetSurface import preparePlanetShader;
|
||||
|
||||
const double PLANET_DIST_MAX = 4000;
|
||||
|
||||
enum PlanetSpecial {
|
||||
PS_None,
|
||||
PS_Asteroids,
|
||||
PS_Ring
|
||||
};
|
||||
|
||||
//Draws the physical star, its corona, and a distant star sprite
|
||||
final class MoonData {
|
||||
uint style = 0;
|
||||
float size = 0.f;
|
||||
};
|
||||
|
||||
final class PlanetNodeScript {
|
||||
bool Colonized = false;
|
||||
const Material@ emptyMat = material::ProceduralPlanet;
|
||||
const Material@ colonyMat = material::ProceduralPlanet;
|
||||
const Material@ atmosMat;
|
||||
const Model@ planetModel;
|
||||
PlanetSpecial special = PS_None;
|
||||
double ringScale = 1.0, ringAngle = 0.0;
|
||||
float ringMin = 0.f, ringMax = 1.f;
|
||||
const Material@ ringMat;
|
||||
array<MoonData@>@ moons;
|
||||
uint gfxFlags = 0;
|
||||
Planet@ obj;
|
||||
|
||||
PlanetNodeScript(Node& node) {
|
||||
node.memorable = true;
|
||||
node.animInvis = false;
|
||||
node.autoCull = false;
|
||||
}
|
||||
|
||||
void set_planetType(Node& node, int planetTypeID) {
|
||||
//Cache values for performance
|
||||
const PlanetType@ type = getPlanetType(planetTypeID);
|
||||
@emptyMat = type.emptyMat;
|
||||
@colonyMat = type.colonyMat;
|
||||
@planetModel = type.model;
|
||||
//@dyingMat = type.dyingMat;
|
||||
@atmosMat = type.atmosMat;
|
||||
node.transparent = atmosMat !is null;
|
||||
}
|
||||
|
||||
void set_colonized(bool isColonized) {
|
||||
Colonized = isColonized;
|
||||
}
|
||||
|
||||
void set_flags(uint newFlags) {
|
||||
gfxFlags = newFlags;
|
||||
}
|
||||
|
||||
void establish(Planet@ obj) {
|
||||
@this.obj = obj;
|
||||
}
|
||||
|
||||
void addRing(Node& node, uint rnd) {
|
||||
node.autoCull = false;
|
||||
special = PS_Ring;
|
||||
|
||||
uint matIndex = rnd % 7;
|
||||
rnd /= 7;
|
||||
|
||||
uint scale = rnd % 256;
|
||||
rnd /= 256;
|
||||
|
||||
uint inner = rnd % 128;
|
||||
rnd /= 128;
|
||||
|
||||
uint outer = rnd % 64;
|
||||
rnd /= 64;
|
||||
|
||||
uint angle = rnd % 64;
|
||||
rnd /= 64;
|
||||
|
||||
ringScale = 1.2 + 2.0 * double(scale) / 1024.0;
|
||||
ringMin = double(inner) * 0.9 / 1024.0;
|
||||
ringMax = max(1.0 - ((1.0 - ringMin) * double(outer)/1024.0), ringMin + 0.1);
|
||||
ringAngle = pi * (-0.07 + (0.14 * double(angle)/64.0));
|
||||
|
||||
@ringMat = getMaterial("PlanetRing" + (1 + matIndex));
|
||||
}
|
||||
|
||||
void addMoon(Node& node, float size, uint style = 0) {
|
||||
if(moons is null)
|
||||
@moons = array<MoonData@>();
|
||||
MoonData dat;
|
||||
dat.size = size;
|
||||
dat.style = style;
|
||||
moons.insertLast(dat);
|
||||
}
|
||||
|
||||
void giveAsteroids(Node& node) {
|
||||
node.autoCull = true;
|
||||
special = PS_Asteroids;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
double visScale = node.abs_scale;
|
||||
double distScale = 1.0;
|
||||
|
||||
if(special == PS_Ring) {
|
||||
visScale *= ringScale * 1.5;
|
||||
}
|
||||
if(moons !is null) {
|
||||
if(node.sortDistance < 800.0 * node.abs_scale)
|
||||
visScale = max(visScale, 100.0 + node.abs_scale);
|
||||
}
|
||||
if(gfxFlags & PGA_Ringworld != 0)
|
||||
distScale = node.abs_scale / 10.0;
|
||||
|
||||
return node.sortDistance * config::GFX_DISTANCE_MOD < PLANET_DIST_MAX * distScale && isSphereVisible(node.abs_position, visScale);
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(gfxFlags & PGA_Ringworld != 0) {
|
||||
double lodDist = node.sortDistance / (node.abs_scale * pixelSizeRatio);
|
||||
node.applyTransform();
|
||||
|
||||
material::GenericPBR_RingworldOuter.switchTo();
|
||||
model::RingworldOuter.draw(lodDist);
|
||||
|
||||
//Poor man's opposite direction rotation
|
||||
applyAbsTransform(vec3d(), vec3d(1.0), node.rotation.inverted());
|
||||
applyAbsTransform(vec3d(), vec3d(1.0), node.rotation.inverted());
|
||||
material::GenericPBR_RingworldInner.switchTo();
|
||||
model::RingworldInner.draw(lodDist);
|
||||
undoTransform();
|
||||
undoTransform();
|
||||
|
||||
preparePlanetShader(obj);
|
||||
getPlanetMaterial(obj, material::RingworldSurface).switchTo();
|
||||
|
||||
model::RingworldLiving.draw(lodDist);
|
||||
|
||||
// material::RingworldAtmo.switchTo();
|
||||
// model::RingworldAtmosphere.draw(lodDist);
|
||||
|
||||
undoTransform();
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasAtmos = atmosMat !is null;
|
||||
if(hasAtmos && node.sortDistance * config::GFX_DISTANCE_MOD < PLANET_DIST_MAX * pixelSizeRatio * node.abs_scale * 0.25)
|
||||
drawBuffers();
|
||||
|
||||
node.applyTransform();
|
||||
|
||||
const Material@ baseMat;
|
||||
if(Colonized)
|
||||
@baseMat = colonyMat;
|
||||
else
|
||||
@baseMat = emptyMat;
|
||||
|
||||
preparePlanetShader(obj);
|
||||
getPlanetMaterial(obj, baseMat).switchTo();
|
||||
planetModel.draw(node.sortDistance / (node.abs_scale * pixelSizeRatio));
|
||||
|
||||
if(hasAtmos) {
|
||||
applyAbsTransform(vec3d(), vec3d(1.015), quaterniond_fromAxisAngle(vec3d_up(), fraction(gameTime / 240.0) * twopi));
|
||||
|
||||
atmosMat.switchTo();
|
||||
//Use the same lod as the planet to avoid weirdness
|
||||
model::Sphere_max.draw(node.sortDistance / (node.abs_scale * pixelSizeRatio));
|
||||
undoTransform();
|
||||
}
|
||||
|
||||
if(special == PS_Asteroids) {
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
applyAbsTransform(vec3d(2.0,2.0,2.0), vec3d(0.01), quaterniond());
|
||||
model::Asteroid1.draw();
|
||||
undoTransform();
|
||||
|
||||
material::AsteroidMagnetite.switchTo();
|
||||
applyAbsTransform(vec3d(2.3,1.5,1.95), vec3d(0.0125), quaterniond_fromAxisAngle(vec3d(0,0.32,-0.1).normalize(), 1.3));
|
||||
model::Asteroid2.draw();
|
||||
undoTransform();
|
||||
|
||||
material::AsteroidTonalite.switchTo();
|
||||
applyAbsTransform(vec3d(2.4,2.8,2.1), vec3d(0.008), quaterniond_fromAxisAngle(vec3d(1).normalize(), 0.782));
|
||||
model::Asteroid3.draw();
|
||||
undoTransform();
|
||||
}
|
||||
else if(special == PS_Ring) {
|
||||
auto ringRot = node.abs_rotation.inverted() *
|
||||
quaterniond_fromAxisAngle(vec3d_front(), ringAngle) *
|
||||
quaterniond_fromAxisAngle(vec3d_up(), ((gameTime / 30.0) % (2.0 * pi)));
|
||||
|
||||
vec3d starDir = node.parent.abs_position - node.abs_position;
|
||||
starDir = (node.abs_rotation * ringRot).inverted() * starDir;
|
||||
|
||||
shader::STAR_DIRECTION = vec2f(starDir.x, starDir.z);
|
||||
shader::PLANET_RING_RATIO = 1.0 / ringScale;
|
||||
shader::RING_MIN = ringMin;
|
||||
shader::RING_MAX = ringMax;
|
||||
|
||||
applyAbsTransform(vec3d(), vec3d(ringScale), ringRot);
|
||||
ringMat.switchTo();
|
||||
model::PlanetRing.draw();
|
||||
undoTransform();
|
||||
}
|
||||
|
||||
//if(gfxFlags & PGA_SpaceElevator != 0) {
|
||||
//TODO
|
||||
//}
|
||||
|
||||
undoTransform();
|
||||
|
||||
if(moons !is null && node.sortDistance < 800.0 * node.abs_scale) {
|
||||
for(uint i = 0, cnt = moons.length; i < cnt; ++i) {
|
||||
auto@ dat = moons[i];
|
||||
|
||||
uint st = dat.style;
|
||||
double rot = fraction(gameTime / (1.0 + 12.0 * double(st % 256) / 255.0)) * twopi;
|
||||
st >>= 8;
|
||||
double angle = fraction(gameTime / (10.0 + 40.0 * double(st % 256) / 255.0)) * twopi;
|
||||
st >>= 8;
|
||||
double distance = double(st % 256) / 255.0 * 7.0 + 2.0;
|
||||
st >>= 8;
|
||||
vec3d offset = quaterniond_fromAxisAngle(vec3d_up(), angle) * vec3d_front(distance * node.abs_scale);
|
||||
|
||||
applyTransform(node.abs_position + offset, vec3d(dat.size), quaterniond_fromAxisAngle(vec3d_up(), rot));
|
||||
material::ProceduralMoon.switchTo();
|
||||
model::Moon_Sphere_max.draw(node.sortDistance / (dat.size * pixelSizeRatio));
|
||||
undoTransform();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DynTex {
|
||||
DynamicTexture@ tex;
|
||||
Object@ obj;
|
||||
double lastRender = 0.0;
|
||||
uint modId = uint(-1);
|
||||
const Material@ baseMat;
|
||||
double delay = 0;
|
||||
|
||||
DynTex() {
|
||||
@tex = DynamicTexture();
|
||||
}
|
||||
|
||||
void cache(Object& obj, const Material@ baseMat) {
|
||||
if(!obj.valid)
|
||||
return;
|
||||
if(this.obj is obj && modId == obj.surfaceModId && baseMat is this.baseMat)
|
||||
return;
|
||||
if(modId != uint(-1) && frameTime < delay && this.obj is obj && baseMat is this.baseMat)
|
||||
return;
|
||||
|
||||
@this.obj = obj;
|
||||
@this.baseMat = baseMat;
|
||||
|
||||
vec2u size = vec2u(obj.originalGridSize);
|
||||
if(size.x == 0 || size.y == 0) {
|
||||
@this.obj = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Image img(size, 4);
|
||||
|
||||
uint shown = obj.getSurfaceData(img);
|
||||
modId = shown;
|
||||
if(shown == uint(-1)) {
|
||||
@this.obj = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const Texture@ prevTex = tex.material.texture7;
|
||||
tex.material = baseMat;
|
||||
@tex.material.texture7 = prevTex;
|
||||
@tex.image[7] = img;
|
||||
delay = frameTime + 1.0;
|
||||
}
|
||||
|
||||
const Material@ get_material() {
|
||||
if(obj is null)
|
||||
return null;
|
||||
tex.stream();
|
||||
lastRender = frameTime;
|
||||
return tex.material;
|
||||
}
|
||||
|
||||
void switchTo() {
|
||||
if(obj is null)
|
||||
return;
|
||||
tex.stream();
|
||||
tex.material.switchTo();
|
||||
lastRender = frameTime;
|
||||
}
|
||||
};
|
||||
|
||||
const uint MIN_CACHED = 8;
|
||||
const uint MAX_CACHED = 32;
|
||||
const double DISCARD_TIME = 2.0;
|
||||
array<DynTex@> cachedTextures;
|
||||
|
||||
DynTex@ getPlanetMaterial(Object& obj, const Material@ baseMat) {
|
||||
DynTex@ tex;
|
||||
|
||||
//Check if we already have this planet in our cache
|
||||
for(uint i = 0, cnt = cachedTextures.length; i < cnt; ++i) {
|
||||
if(cachedTextures[i].obj is obj) {
|
||||
@tex = cachedTextures[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if there's any old caches we can override
|
||||
if(tex is null && cachedTextures.length > MIN_CACHED) {
|
||||
for(uint i = 0, cnt = cachedTextures.length; i < cnt; ++i) {
|
||||
if(frameTime - cachedTextures[i].lastRender > DISCARD_TIME) {
|
||||
@tex = cachedTextures[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if we can create a new cache
|
||||
if(tex is null && cachedTextures.length < MAX_CACHED) {
|
||||
@tex = DynTex();
|
||||
cachedTextures.insertLast(tex);
|
||||
}
|
||||
|
||||
//Forcefully override the least recently used cache
|
||||
if(tex is null) {
|
||||
double bestTime = INFINITY;
|
||||
for(uint i = 0, cnt = cachedTextures.length; i < cnt; ++i) {
|
||||
double rt = cachedTextures[i].lastRender;
|
||||
if(rt < bestTime) {
|
||||
@tex = cachedTextures[i];
|
||||
bestTime = rt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tex.cache(obj, baseMat);
|
||||
return tex;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
final class ShipyardNodeScript {
|
||||
const Model@ shipyard = getModel("Shipyard");
|
||||
const Material@ shipyard_mat = getMaterial("VolkurGenericPBR");
|
||||
const Design@ design;
|
||||
float progress = 0;
|
||||
|
||||
ShipyardNodeScript(Node& node) {
|
||||
}
|
||||
|
||||
void updateProgress(const Design@ dsgn, float percent) {
|
||||
@design = dsgn;
|
||||
progress = percent;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
node.applyTransform();
|
||||
|
||||
shipyard_mat.switchTo();
|
||||
shipyard.draw();
|
||||
|
||||
if(design !is null) {
|
||||
applyAbsTransform(vec3d(), vec3d((design.size + 8.0) / (design.size + 16.0)), quaterniond());
|
||||
design.hull.material.switchTo();
|
||||
design.hull.model.draw();
|
||||
undoTransform();
|
||||
}
|
||||
|
||||
undoTransform();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
//Draws the physical star, its corona, and a distant star sprite
|
||||
final class StarNodeScript {
|
||||
//double coronaRot = randomd(-pi,pi);
|
||||
|
||||
StarNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.customColor = true;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
double dist = node.sortDistance / (250.0 * node.abs_scale * pixelSizeRatio);
|
||||
|
||||
if(dist < 1.0) {
|
||||
drawBuffers();
|
||||
node.applyTransform();
|
||||
|
||||
material::StarSurface.switchTo();
|
||||
model::Sphere_max.draw(node.sortDistance / (node.abs_scale * pixelSizeRatio));
|
||||
|
||||
/*vec4f pos;
|
||||
for(uint i = 0; i < 8; ++i) {
|
||||
float angle = float(int(i) - 6) * -0.24f * pi;
|
||||
pos.x = cos(angle);
|
||||
pos.y = sin(angle);
|
||||
pos.w = angle + (pi * 0.5);
|
||||
|
||||
shader::BEZIER_POINTS[i] = pos;
|
||||
}
|
||||
|
||||
applyAbsTransform(vec3d(0,1.0,0), vec3d(0.2), quaterniond());
|
||||
material::Prominence.switchTo();
|
||||
model::Prominence.draw();
|
||||
undoTransform();*/
|
||||
|
||||
undoTransform();
|
||||
|
||||
vec3d upLeft, upRight, center = node.abs_position;
|
||||
getBillboardVecs(center, upLeft, upRight, 0.0);
|
||||
|
||||
upLeft *= node.abs_scale * 1.5;
|
||||
upRight *= node.abs_scale * 1.5;
|
||||
|
||||
drawPolygonStart(PT_Quads, 1, material::Corona);
|
||||
drawPolygonPoint(center + upLeft, vec2f(0,0));
|
||||
drawPolygonPoint(center + upRight, vec2f(1,0));
|
||||
drawPolygonPoint(center - upLeft, vec2f(1,1));
|
||||
drawPolygonPoint(center - upRight, vec2f(0,1));
|
||||
drawPolygonEnd();
|
||||
}
|
||||
|
||||
if(dist > 0.5) {
|
||||
Color col(node.color);
|
||||
col.a = dist > 1.0 ? 255 : int((dist - 0.5)*255.0/0.5);
|
||||
renderBillboard(material::DistantStar, node.abs_position, node.abs_scale * 32.0, 0.0, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,226 @@
|
||||
export StrategicIcon;
|
||||
|
||||
bool SHOW_STRATEGIC_ICONS = true;
|
||||
|
||||
bool getStrategicIconsShown() {
|
||||
return SHOW_STRATEGIC_ICONS;
|
||||
}
|
||||
|
||||
void setStrategicIconsShown(bool enabled) {
|
||||
SHOW_STRATEGIC_ICONS = enabled;
|
||||
}
|
||||
|
||||
const double POS_CHANGE_THRES = 1000.0;
|
||||
class StrategicIcon {
|
||||
double animate_pct = 0;
|
||||
float lastDist = -1e10f;
|
||||
float alpha = 1.f;
|
||||
vec3d strategicPos;
|
||||
vec3d originPos;
|
||||
vec3d animPos;
|
||||
bool wasVisible = false;
|
||||
|
||||
StrategicIcon(Node& node) {
|
||||
node.transparent = true;
|
||||
node.fixedSize = true;
|
||||
node.needsTransform = false;
|
||||
node.createPhysics();
|
||||
}
|
||||
|
||||
void setStrategic(Node& node, vec3d position, vec3d origin) {
|
||||
if(animate_pct > 0 && animate_pct < 1)
|
||||
return;
|
||||
|
||||
if(wasVisible && animate_pct >= 1 && strategicPos.distanceToSQ(position) > POS_CHANGE_THRES) {
|
||||
animPos = strategicPos;
|
||||
animate_pct = 0;
|
||||
}
|
||||
strategicPos = position;
|
||||
originPos = origin;
|
||||
}
|
||||
|
||||
void clearStrategic() {
|
||||
originPos = vec3d();
|
||||
strategicPos = vec3d();
|
||||
}
|
||||
|
||||
void update(Node& node, const vec3d& realPos, double SIZE, double OUTSIDE_DISTANCE, double OUTSIDE_SIZE_MAX,
|
||||
double ANIMATE_TIME, double FADE_DIST_MIN, double FADE_DIST_MAX)
|
||||
{
|
||||
vec3d real = animPos.zero ? realPos : animPos;
|
||||
vec3d origin = originPos.zero ? real : originPos;
|
||||
vec3d strategic = strategicPos.zero ? real : strategicPos;
|
||||
|
||||
//Figure out animation state
|
||||
double originDist = getCameraDistance(origin) * config::GFX_DISTANCE_MOD;
|
||||
double dist = node.sortDistance * config::GFX_DISTANCE_MOD;
|
||||
if(abs(dist - lastDist) > OUTSIDE_DISTANCE / 2 || !wasVisible) {
|
||||
//Snap when changing distance rapidly
|
||||
if(originDist > OUTSIDE_DISTANCE)
|
||||
animate_pct = 1.f;
|
||||
else
|
||||
animate_pct = 0.f;
|
||||
}
|
||||
else {
|
||||
//Animate moving to the edge of the system
|
||||
if(originDist > OUTSIDE_DISTANCE)
|
||||
animate_pct = min(animate_pct + frameLength / ANIMATE_TIME, 1.0);
|
||||
else
|
||||
animate_pct = max(0.0, animate_pct - frameLength / ANIMATE_TIME);
|
||||
}
|
||||
|
||||
//Do fading
|
||||
if(dist < FADE_DIST_MAX) {
|
||||
if(dist < FADE_DIST_MIN)
|
||||
alpha = 0.f;
|
||||
else
|
||||
alpha = (dist - FADE_DIST_MIN) / (FADE_DIST_MAX - FADE_DIST_MIN);
|
||||
}
|
||||
else {
|
||||
alpha = 1.f;
|
||||
}
|
||||
|
||||
//Figure out animated position and size
|
||||
vec3d pos;
|
||||
double width;
|
||||
if(animate_pct > 0) {
|
||||
pos = real.interpolate(strategic, animate_pct);
|
||||
width = SIZE * min(node.sortDistance, OUTSIDE_SIZE_MAX / config::GFX_DISTANCE_MOD) / (pixelSizeRatio / uiScale);
|
||||
|
||||
if(animate_pct >= 1)
|
||||
animPos = vec3d();
|
||||
}
|
||||
else {
|
||||
pos = real;
|
||||
width = SIZE * node.sortDistance / (pixelSizeRatio / uiScale);
|
||||
}
|
||||
|
||||
wasVisible = true;
|
||||
|
||||
if(node.position != pos || node.scale != width) {
|
||||
node.scale = width;
|
||||
node.position = pos;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
lastDist = dist;
|
||||
}
|
||||
};
|
||||
|
||||
const double OUTSIDE_DISTANCE = 12000.0;
|
||||
const double OUTSIDE_SIZE_MAX = 25000.0;
|
||||
const double ANIMATE_TIME = 0.45;
|
||||
|
||||
const double FADE_DIST_MIN = 750;
|
||||
const double FADE_DIST_MAX = 1000;
|
||||
|
||||
class StrategicIconNodeScript : StrategicIcon {
|
||||
bool ownerColor = true;
|
||||
Object@ object;
|
||||
const SpriteSheet@ sheet;
|
||||
const Material@ mat;
|
||||
uint icon;
|
||||
Color color;
|
||||
double size;
|
||||
Region@ region;
|
||||
|
||||
StrategicIconNodeScript(Node& node) {
|
||||
node.transparent = true;
|
||||
node.visible = false;
|
||||
node.needsTransform = false;
|
||||
node.fixedSize = true;
|
||||
node.createPhysics();
|
||||
super(node);
|
||||
}
|
||||
|
||||
void establish(Node& node, Object@ obj, double Size, const SpriteSheet& sht, uint index) {
|
||||
@sheet = sht;
|
||||
icon = index;
|
||||
size = Size;
|
||||
establish(node, obj);
|
||||
}
|
||||
|
||||
void establish(Node& node, Object@ obj, double Size, const Material& material) {
|
||||
@mat = material;
|
||||
icon = 0;
|
||||
size = Size;
|
||||
establish(node, obj);
|
||||
}
|
||||
|
||||
void establish(Node& node, Object@ obj) {
|
||||
@node.object = obj;
|
||||
@object = obj;
|
||||
@region = obj.region;
|
||||
node.hintParentObject(region, false);
|
||||
node.position = obj.position;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void setColor(Node& node, uint col) {
|
||||
ownerColor = false;
|
||||
color = Color(col);
|
||||
node.color = Colorf(color);
|
||||
}
|
||||
|
||||
void clearColor() {
|
||||
ownerColor = true;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(object is null)
|
||||
return false;
|
||||
|
||||
double objDist = getCameraDistance(object.position);
|
||||
double fadeDist = 1.0e4 * max(5.0, object.radius);
|
||||
if(objDist > fadeDist)
|
||||
return false;
|
||||
|
||||
bool isMemory = false;
|
||||
if(object.visible) {
|
||||
node.visible = true;
|
||||
}
|
||||
else if(node.memorable && object.known) {
|
||||
node.visible = true;
|
||||
isMemory = true;
|
||||
}
|
||||
else if(!node.visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(object.region !is region) {
|
||||
@region = object.region;
|
||||
node.hintParentObject(region, false);
|
||||
}
|
||||
|
||||
double dist = node.sortDistance * config::GFX_DISTANCE_MOD;
|
||||
|
||||
StrategicIcon::update(node, object.node_position, size,
|
||||
OUTSIDE_DISTANCE, OUTSIDE_SIZE_MAX, ANIMATE_TIME,
|
||||
FADE_DIST_MIN, FADE_DIST_MAX);
|
||||
|
||||
if(isMemory)
|
||||
alpha *= 0.7f;
|
||||
if(objDist > 0.8 * fadeDist)
|
||||
alpha *= 1.0 - (objDist - 0.8 * fadeDist) / (0.2 * fadeDist);
|
||||
|
||||
if(alpha < 0.004f || !SHOW_STRATEGIC_ICONS)
|
||||
return false;
|
||||
|
||||
node.color.a = alpha;
|
||||
if(ownerColor) {
|
||||
Empire@ owner = object.owner;
|
||||
if(owner !is null) {
|
||||
color = owner.color;
|
||||
color.a = 0xff * alpha;
|
||||
node.color = Colorf(color);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(sheet !is null)
|
||||
renderBillboard(sheet, icon, node.abs_position, node.scale, 0, color);
|
||||
else if(mat !is null)
|
||||
renderBillboard(mat, node.abs_position, node.scale, 0, color);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,445 @@
|
||||
import planet_loyalty;
|
||||
|
||||
vec4f ContestedVec;
|
||||
vec4f GainingVec;
|
||||
vec4f LosingVec;
|
||||
vec4f ProtectedVec;
|
||||
vec4f ZealotVec;
|
||||
|
||||
enum DebrisType {
|
||||
DT_Rock00,
|
||||
DT_Rock01,
|
||||
DT_Rock02,
|
||||
DT_Rock03,
|
||||
DT_Rock04,
|
||||
DT_Rock05,
|
||||
DT_Rock06,
|
||||
DT_Rock07,
|
||||
DT_Rock08,
|
||||
DT_Rock09,
|
||||
DT_Rock10,
|
||||
DT_Rock11,
|
||||
|
||||
DT_Metal00,
|
||||
DT_Metal01,
|
||||
DT_Metal02,
|
||||
DT_Metal03,
|
||||
DT_Metal04,
|
||||
DT_Metal05,
|
||||
DT_Metal06,
|
||||
DT_Metal07,
|
||||
DT_Metal08,
|
||||
DT_Metal09,
|
||||
DT_Metal10,
|
||||
DT_Metal11,
|
||||
DT_Metal12,
|
||||
DT_Metal13,
|
||||
DT_Metal14,
|
||||
DT_Metal15,
|
||||
DT_Metal16,
|
||||
DT_Metal17,
|
||||
DT_Metal18,
|
||||
DT_Metal19,
|
||||
DT_Metal20,
|
||||
DT_Metal21,
|
||||
DT_Metal22,
|
||||
DT_Metal23,
|
||||
DT_Metal24,
|
||||
DT_Metal25,
|
||||
DT_Metal26,
|
||||
DT_Metal27,
|
||||
DT_Metal28,
|
||||
DT_Metal29,
|
||||
DT_Metal30,
|
||||
DT_Metal31,
|
||||
DT_Metal32,
|
||||
DT_Metal33,
|
||||
DT_Metal34,
|
||||
DT_Metal35,
|
||||
|
||||
DT_COUNT,
|
||||
|
||||
DT_ROCK_START = DT_Rock00,
|
||||
DT_ROCK_END = DT_Rock11,
|
||||
|
||||
DT_METAL_START = DT_Metal00,
|
||||
DT_METAL_END = DT_Metal35
|
||||
};
|
||||
|
||||
void init() {
|
||||
ContestedColors[CM_Contested].toVec4(ContestedVec);
|
||||
ContestedVec.w = 0.5f;
|
||||
|
||||
ContestedColors[CM_GainingLoyalty].toVec4(GainingVec);
|
||||
GainingVec.w = 0.5f;
|
||||
|
||||
ContestedColors[CM_LosingLoyalty].toVec4(LosingVec);
|
||||
LosingVec.w = 0.5f;
|
||||
|
||||
ContestedColors[CM_Protected].toVec4(ProtectedVec);
|
||||
ProtectedVec.w = 0.5f;
|
||||
|
||||
ContestedColors[CM_Zealot].toVec4(ZealotVec);
|
||||
ZealotVec.w = 0.5f;
|
||||
}
|
||||
|
||||
bool SHOW_SYSTEM_PLANES = true;
|
||||
void setSystemPlanesShown(bool enabled) {
|
||||
SHOW_SYSTEM_PLANES = enabled;
|
||||
}
|
||||
|
||||
bool getSystemPlanesShown() {
|
||||
return SHOW_SYSTEM_PLANES;
|
||||
}
|
||||
|
||||
final class SystemDebris {
|
||||
DebrisType type;
|
||||
quaterniond rot;
|
||||
vec3d pos, vel, axis;
|
||||
float scale, life, rotSpeed;
|
||||
float spawnTime;
|
||||
bool draw = false;
|
||||
double dist = 0;
|
||||
|
||||
int opCmp(const SystemDebris& other) {
|
||||
return int(type) - int(other.type);
|
||||
}
|
||||
};
|
||||
|
||||
class SystemPlaneNodeScript {
|
||||
Region@ obj;
|
||||
vec3d origin;
|
||||
float outerRadius;
|
||||
float innerRadius;
|
||||
uint contested = CM_None;
|
||||
bool decaying = false;
|
||||
Color primaryColor;
|
||||
float alpha = 1.f;
|
||||
|
||||
bool drawPlane = false, drawDebris = false;
|
||||
array<SystemDebris@> debris;
|
||||
|
||||
SystemPlaneNodeScript(Node& node) {
|
||||
}
|
||||
|
||||
void setContested(uint mode) {
|
||||
contested = mode;
|
||||
}
|
||||
|
||||
void establish(Node& node, Region& region) {
|
||||
@obj = region;
|
||||
origin = region.position;
|
||||
outerRadius = region.OuterRadius;
|
||||
innerRadius = region.InnerRadius;
|
||||
|
||||
node.scale = region.radius;
|
||||
node.position = origin;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void setPrimaryEmpire(Empire@ emp) {
|
||||
if(emp !is null)
|
||||
primaryColor = emp.color;
|
||||
else
|
||||
primaryColor = Color(0xaaaaaaff);
|
||||
}
|
||||
|
||||
void addMetalDebris(vec3d position, uint count = 1) {
|
||||
double spread = double(count) - 1.0;
|
||||
|
||||
for(uint i = 0; i < count; ++i) {
|
||||
SystemDebris d;
|
||||
d.type = DebrisType(randomi(DT_METAL_START,DT_METAL_END));
|
||||
vec2d off = random2d(200.0, innerRadius);
|
||||
d.pos = position + random3d(spread);
|
||||
d.scale = randomd(1.0,2.0);
|
||||
d.axis = random3d(1.0);
|
||||
d.rotSpeed = randomd(pi * -0.125, pi * 0.125);
|
||||
double dist = cameraPos.distanceTo(d.pos);
|
||||
d.vel = random3d(-8.0,8.0);
|
||||
d.life = randomd(30.0,60.0);
|
||||
d.spawnTime = frameGameTime;
|
||||
d.rot = quaterniond_fromAxisAngle(random3d(1.0), randomd(0.0,twopi));
|
||||
debris.insertLast(d);
|
||||
}
|
||||
}
|
||||
|
||||
void generateDebris() {
|
||||
uint count = uint(innerRadius * settings::dSystemDebris / 10.0);
|
||||
if(debris.length < count) {
|
||||
for(uint i = debris.length; i < count; ++i) {
|
||||
SystemDebris d;
|
||||
d.type = DebrisType(randomi(DT_ROCK_START,DT_ROCK_END));
|
||||
vec2d off = random2d(200.0, innerRadius);
|
||||
d.pos = origin + vec3d(off.x, randomd(-15.0,15.0), off.y);
|
||||
d.scale = randomd(1.0,2.0);
|
||||
d.axis = random3d(1.0);
|
||||
d.rotSpeed = randomd(pi * -0.125, pi * 0.125);
|
||||
double dist = cameraPos.distanceTo(d.pos);
|
||||
if(dist < pixelSizeRatio * 2000.0 * d.scale && isSphereVisible(d.pos, d.scale))
|
||||
continue;
|
||||
|
||||
d.vel = random3d(-8.0,8.0);
|
||||
d.life = randomd(30.0,60.0);
|
||||
d.spawnTime = frameGameTime;
|
||||
d.rot = quaterniond_fromAxisAngle(random3d(1.0), randomd(0.0,twopi));
|
||||
debris.insertLast(d);
|
||||
}
|
||||
|
||||
debris.sortAsc();
|
||||
}
|
||||
}
|
||||
|
||||
void tickDebris(double time) {
|
||||
vec3d cam = cameraPos;
|
||||
for(int i = int(debris.length-1); i >= 0; --i) {
|
||||
auto@ d = debris[i];
|
||||
d.life -= time;
|
||||
d.pos += d.vel * time;
|
||||
d.dist = cam.distanceTo(d.pos) / (pixelSizeRatio * d.scale);
|
||||
d.draw = d.dist < 2000.0 && isSphereVisible(d.pos, d.scale);
|
||||
|
||||
if(d.life <= 0 && !d.draw) {
|
||||
debris.removeAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(d.draw)
|
||||
d.rot = quaterniond_fromAxisAngle(d.axis, d.rotSpeed * time) * d.rot;
|
||||
}
|
||||
}
|
||||
|
||||
void renderDebris() {
|
||||
float curTime = frameGameTime;
|
||||
for(uint i = 0, cnt = debris.length; i < cnt; ++i) {
|
||||
auto@ d = debris[i];
|
||||
if(!d.draw)
|
||||
continue;
|
||||
|
||||
applyTransform(d.pos, d.scale, d.rot);
|
||||
shader::LIFE = curTime - d.spawnTime;
|
||||
switch(d.type) {
|
||||
case DT_Rock00:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid1_lod2.draw(d.dist); break;
|
||||
case DT_Rock01:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid2_lod2.draw(d.dist); break;
|
||||
case DT_Rock02:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid3_lod2.draw(d.dist); break;
|
||||
case DT_Rock03:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid4_lod2.draw(d.dist); break;
|
||||
case DT_Rock04:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid1_lod2.draw(d.dist); break;
|
||||
case DT_Rock05:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid2_lod2.draw(d.dist); break;
|
||||
case DT_Rock06:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid3_lod2.draw(d.dist); break;
|
||||
case DT_Rock07:
|
||||
material::AsteroidPegmatite.switchTo();
|
||||
model::Asteroid4_lod2.draw(d.dist); break;
|
||||
case DT_Rock08:
|
||||
material::AsteroidMagnetite.switchTo();
|
||||
model::Asteroid1_lod2.draw(d.dist); break;
|
||||
case DT_Rock09:
|
||||
material::AsteroidMagnetite.switchTo();
|
||||
model::Asteroid2_lod2.draw(d.dist); break;
|
||||
case DT_Rock10:
|
||||
material::AsteroidMagnetite.switchTo();
|
||||
model::Asteroid3_lod2.draw(d.dist); break;
|
||||
case DT_Rock11:
|
||||
material::AsteroidMagnetite.switchTo();
|
||||
model::Asteroid4_lod2.draw(d.dist); break;
|
||||
case DT_Metal00:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage00.draw(d.dist); break;
|
||||
case DT_Metal01:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage01.draw(d.dist); break;
|
||||
case DT_Metal02:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage02.draw(d.dist); break;
|
||||
case DT_Metal03:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage03.draw(d.dist); break;
|
||||
case DT_Metal04:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage04.draw(d.dist); break;
|
||||
case DT_Metal05:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage05.draw(d.dist); break;
|
||||
case DT_Metal06:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage06.draw(d.dist); break;
|
||||
case DT_Metal07:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage07.draw(d.dist); break;
|
||||
case DT_Metal08:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage08.draw(d.dist); break;
|
||||
case DT_Metal09:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage09.draw(d.dist); break;
|
||||
case DT_Metal10:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage10.draw(d.dist); break;
|
||||
case DT_Metal11:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage11.draw(d.dist); break;
|
||||
case DT_Metal12:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage12.draw(d.dist); break;
|
||||
case DT_Metal13:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage13.draw(d.dist); break;
|
||||
case DT_Metal14:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage14.draw(d.dist); break;
|
||||
case DT_Metal15:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage15.draw(d.dist); break;
|
||||
case DT_Metal16:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage16.draw(d.dist); break;
|
||||
case DT_Metal17:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage17.draw(d.dist); break;
|
||||
case DT_Metal18:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage18.draw(d.dist); break;
|
||||
case DT_Metal19:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage19.draw(d.dist); break;
|
||||
case DT_Metal20:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage20.draw(d.dist); break;
|
||||
case DT_Metal21:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage21.draw(d.dist); break;
|
||||
case DT_Metal22:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage22.draw(d.dist); break;
|
||||
case DT_Metal23:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage23.draw(d.dist); break;
|
||||
case DT_Metal24:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage24.draw(d.dist); break;
|
||||
case DT_Metal25:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage25.draw(d.dist); break;
|
||||
case DT_Metal26:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage26.draw(d.dist); break;
|
||||
case DT_Metal27:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage27.draw(d.dist); break;
|
||||
case DT_Metal28:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage28.draw(d.dist); break;
|
||||
case DT_Metal29:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage29.draw(d.dist); break;
|
||||
case DT_Metal30:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage30.draw(d.dist); break;
|
||||
case DT_Metal31:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage31.draw(d.dist); break;
|
||||
case DT_Metal32:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage32.draw(d.dist); break;
|
||||
case DT_Metal33:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage33.draw(d.dist); break;
|
||||
case DT_Metal34:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage34.draw(d.dist); break;
|
||||
case DT_Metal35:
|
||||
material::Debris.switchTo();
|
||||
model::Wreckage35.draw(d.dist); break;
|
||||
|
||||
}
|
||||
undoTransform();
|
||||
}
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(playerEmpire !is null && playerEmpire.valid && obj.ExploredMask & playerEmpire.visionMask == 0)
|
||||
return false;
|
||||
|
||||
double d = node.abs_scale * pixelSizeRatio;
|
||||
drawPlane = SHOW_SYSTEM_PLANES && (node.sortDistance < 200.0 * d);
|
||||
drawDebris = node.sortDistance < 5.0 * node.abs_scale * pixelSizeRatio;
|
||||
|
||||
alpha = 1.0 - clamp((node.sortDistance - 150.0 * d) / (50.0 * d), 0.0, 1.0);
|
||||
|
||||
if(drawDebris) {
|
||||
tickDebris(frameLength * gameSpeed);
|
||||
generateDebris();
|
||||
}
|
||||
|
||||
return drawPlane || drawDebris;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(drawDebris)
|
||||
renderDebris();
|
||||
|
||||
if(drawPlane) {
|
||||
shader::RADIUS = outerRadius;
|
||||
shader::INNER_RADIUS = innerRadius;
|
||||
|
||||
//Calculate distance to plane
|
||||
line3dd camLine(cameraPos, cameraPos+cameraFacing);
|
||||
vec3d intersect;
|
||||
if(!camLine.intersectY(intersect, obj.position.y, false)) {
|
||||
intersect = cameraPos;
|
||||
intersect.y = obj.position.y;
|
||||
shader::PLANE_DISTANCE = sqrt(
|
||||
sqr(max(0.0, intersect.distanceTo(obj.position) - outerRadius))
|
||||
+ sqr(cameraPos.y - obj.position.y));
|
||||
}
|
||||
else {
|
||||
shader::PLANE_DISTANCE = intersect.distanceTo(cameraPos);
|
||||
max(0.0, intersect.distanceTo(obj.position) - outerRadius);
|
||||
}
|
||||
|
||||
switch(contested) {
|
||||
case CM_None:
|
||||
shader::GLOW_COLOR.w = 0.f;
|
||||
break;
|
||||
case CM_Contested:
|
||||
shader::GLOW_COLOR = ContestedVec;
|
||||
break;
|
||||
case CM_LosingLoyalty:
|
||||
shader::GLOW_COLOR = LosingVec;
|
||||
break;
|
||||
case CM_GainingLoyalty:
|
||||
shader::GLOW_COLOR = GainingVec;
|
||||
break;
|
||||
case CM_Protected:
|
||||
shader::GLOW_COLOR = ProtectedVec;
|
||||
break;
|
||||
case CM_Zealot:
|
||||
shader::GLOW_COLOR = ZealotVec;
|
||||
break;
|
||||
}
|
||||
|
||||
Color c = primaryColor;
|
||||
c.a = uint8(alpha * 255.f);
|
||||
|
||||
drawPolygonStart(PT_Quads, 1, material::SystemPlane);
|
||||
drawPolygonPoint(origin + vec3d(-outerRadius, 0, -outerRadius), vec2f(0.f, 0.f), c);
|
||||
drawPolygonPoint(origin + vec3d(+outerRadius, 0, -outerRadius), vec2f(1.f, 0.f));
|
||||
drawPolygonPoint(origin + vec3d(+outerRadius, 0, +outerRadius), vec2f(1.f, 1.f));
|
||||
drawPolygonPoint(origin + vec3d(-outerRadius, 0, +outerRadius), vec2f(0.f, 1.f));
|
||||
drawPolygonEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,351 @@
|
||||
//Radius added to every entry
|
||||
const double ExtraRadius = 100.0;
|
||||
const double BorderThickness = 150.0;
|
||||
|
||||
bool SHOW_TERRITORY_BORDERS = true;
|
||||
void setTerritoryBordersShown(bool enabled) {
|
||||
SHOW_TERRITORY_BORDERS = enabled;
|
||||
}
|
||||
|
||||
bool getTerritoryBordersShown() {
|
||||
return SHOW_TERRITORY_BORDERS;
|
||||
}
|
||||
|
||||
bool angleFacing(double a, double b) {
|
||||
double diff = angleDiff(a,b);
|
||||
return diff > -pi * 0.5 && diff < pi * 0.5;
|
||||
}
|
||||
|
||||
final class Sphere {
|
||||
vec3d pos;
|
||||
double radius;
|
||||
double angle;
|
||||
double dist;
|
||||
int id;
|
||||
int count = 1;
|
||||
bool edge;
|
||||
|
||||
Sphere(int ID, const vec3d& Pos, double Radius, bool Edge) {
|
||||
id = ID;
|
||||
radius = Radius;
|
||||
pos = Pos;
|
||||
edge = Edge;
|
||||
}
|
||||
|
||||
void updateAngle(const vec3d& center) {
|
||||
angle = vec2d(pos.x - center.x, pos.z - center.z).radians();
|
||||
dist = center.distanceTo(pos);
|
||||
}
|
||||
|
||||
int opCmp(const Sphere@ other) const {
|
||||
double diff = angle - other.angle;
|
||||
if(diff > 0)
|
||||
return 1;
|
||||
else if(diff < 0)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool include(Sphere@ left, Sphere@ right) {
|
||||
if(dist < left.dist && abs(angleDiff(left.angle, angle)) < pi * (5.0/180.0))
|
||||
return false;
|
||||
if(dist < right.dist && abs(angleDiff(right.angle, angle)) < pi * (5.0/180.0))
|
||||
return false;
|
||||
|
||||
double leftTangent = vec2d(left.pos.x - pos.x, left.pos.z - pos.z).radians() - (pi * 0.5);
|
||||
if(!angleFacing(leftTangent, angle))
|
||||
leftTangent += pi;
|
||||
|
||||
double rightTangent = vec2d(right.pos.x - pos.x, right.pos.z - pos.z).radians() - (pi * 0.5);
|
||||
if(!angleFacing(rightTangent, angle))
|
||||
rightTangent += pi;
|
||||
|
||||
double diff = angleDiff(rightTangent,leftTangent);
|
||||
|
||||
return diff > -pi * 0.31;
|
||||
}
|
||||
|
||||
bool generateVerts(Sphere@ left, Sphere@ right, array<vec3d>@ verts) {
|
||||
//Get angle to tangent collision, choosing the tangent facing away from the center
|
||||
double leftTangent = vec2d(left.pos.x - pos.x, left.pos.z - pos.z).radians() - (pi * 0.5);
|
||||
if(!angleFacing(leftTangent, angle))
|
||||
leftTangent += pi;
|
||||
|
||||
double rightTangent = vec2d(right.pos.x - pos.x, right.pos.z - pos.z).radians() - (pi * 0.5);
|
||||
if(!angleFacing(rightTangent, angle))
|
||||
rightTangent += pi;
|
||||
|
||||
double diff = angleDiff(rightTangent,leftTangent);
|
||||
|
||||
//Curve around the edge of this sphere
|
||||
if(diff > 0) {
|
||||
int points = max(3, int(diff / 0.1309)); //7.5 degrees per point
|
||||
|
||||
for(int i = 0; i < points; ++i) {
|
||||
double a = leftTangent + (diff * double(i) / double(points));
|
||||
vec3d dir(cos(a), 0, sin(a));
|
||||
verts.insertLast(pos + dir * radius);
|
||||
verts.insertLast(pos + dir * (radius - BorderThickness));
|
||||
}
|
||||
}
|
||||
else if(diff > -pi * 0.31) {
|
||||
//Bezier interpolation between tangent lines
|
||||
vec3d leftDir(cos(leftTangent), 0, sin(leftTangent));
|
||||
line3dd leftLine = line3dd(left.pos + leftDir * left.radius, pos + leftDir * radius);
|
||||
|
||||
vec3d rightDir(cos(rightTangent), 0, sin(rightTangent));
|
||||
line3dd rightLine = line3dd(right.pos + rightDir * right.radius, pos + rightDir * radius);
|
||||
|
||||
for(int i = 0; i < 15; ++i) {
|
||||
double p = 0.1 + (0.8 * double(i)/14.0);
|
||||
|
||||
verts.insertLast(leftLine.midpoint.interpolate(leftLine.end, p).interpolate( rightLine.end.interpolate(rightLine.midpoint, p), p));
|
||||
verts.insertLast(verts[verts.length-1] + leftDir.interpolate(rightDir, p).normalized(-BorderThickness));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class TerritoryNodeScript {
|
||||
vec3d inner_center, edge_center;
|
||||
array<vec3d> inner_verts, edge_verts;
|
||||
array<Sphere@> spheres;
|
||||
int innerCount = 0;
|
||||
bool delta = false;
|
||||
Empire@ owner = defaultEmpire;
|
||||
|
||||
TerritoryNodeScript(Node& node) {
|
||||
node.scale = 1000000000.0;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void setOwner(Empire@ emp) {
|
||||
@owner = emp;
|
||||
}
|
||||
|
||||
void addInner(Node& node, int sysId, vec3d position, double radius) {
|
||||
for(uint i = 0, cnt = spheres.length; i < cnt; ++i) {
|
||||
if(spheres[i].id == sysId) {
|
||||
if(spheres[i].edge) {
|
||||
++innerCount;
|
||||
node.visible = true;
|
||||
}
|
||||
spheres[i].count++;
|
||||
spheres[i].edge = false;
|
||||
delta = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
spheres.insertLast(Sphere(sysId, position, radius + ExtraRadius, false));
|
||||
delta = true;
|
||||
|
||||
++innerCount;
|
||||
node.visible = true;
|
||||
}
|
||||
|
||||
void addEdge(int sysId, vec3d position, double radius) {
|
||||
for(uint i = 0, cnt = spheres.length; i < cnt; ++i) {
|
||||
if(spheres[i].id == sysId) {
|
||||
spheres[i].count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
spheres.insertLast(Sphere(sysId, position, radius + ExtraRadius, true));
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void removeInner(Node& node, int sysId) {
|
||||
for(uint i = 0, cnt = spheres.length; i < cnt; ++i) {
|
||||
if(spheres[i].id == sysId) {
|
||||
if(--spheres[i].count == 0) {
|
||||
spheres.removeAt(i);
|
||||
delta = true;
|
||||
}
|
||||
else {
|
||||
spheres[i].edge = true;
|
||||
delta = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
--innerCount;
|
||||
node.visible = innerCount != 0;
|
||||
}
|
||||
|
||||
void removeEdge(int sysId) {
|
||||
for(uint i = 0, cnt = spheres.length; i < cnt; ++i) {
|
||||
if(spheres[i].id == sysId) {
|
||||
if(--spheres[i].count == 0) {
|
||||
spheres.removeAt(i);
|
||||
delta = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rebuildPortion(array<Sphere@>& regions, array<vec3d>& verts, vec3d& center) {
|
||||
center.set(0,0,0);
|
||||
if(regions.length == 0) {
|
||||
verts.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
double rad = 0;
|
||||
for(uint i = 0; i < regions.length; ++i) {
|
||||
center += regions[i].pos;
|
||||
rad = max(rad, regions[i].radius);
|
||||
}
|
||||
center /= double(regions.length);
|
||||
|
||||
//Update the angles for each sphere and sort
|
||||
for(uint i = 0; i < regions.length; ++i) {
|
||||
regions[i].updateAngle(center);
|
||||
regions[i].radius = rad;
|
||||
}
|
||||
|
||||
regions.sortAsc();
|
||||
array<Sphere@>@ final = array<Sphere@>(), temp = @regions;
|
||||
|
||||
if(temp.length > 1) {
|
||||
bool changed = false;
|
||||
while(true) {
|
||||
Sphere@ prev = temp[temp.length-1];
|
||||
for(int i = 0, cnt = temp.length; i < cnt; ++i) {
|
||||
Sphere@ sphere = temp[i];
|
||||
Sphere@ right = temp[(i+1) % cnt];
|
||||
if(sphere.include(prev, right)) {
|
||||
final.insertLast(sphere);
|
||||
@prev = sphere;
|
||||
}
|
||||
else {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(final.length < 3)
|
||||
break;
|
||||
|
||||
if(changed) {
|
||||
changed = false;
|
||||
@temp = @final;
|
||||
@final = array<Sphere@>();
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
final.insertLast(temp[0]);
|
||||
}
|
||||
|
||||
verts.length = 0;
|
||||
if(final.length == 1) {
|
||||
Sphere@ sphere = final[0];
|
||||
for(int i = 0; i < 48; ++i) {
|
||||
double a = double(i) * twopi / 48.0;
|
||||
vec3d dir(cos(a), 0, sin(a));
|
||||
verts.insertLast(sphere.pos + dir * sphere.radius);
|
||||
verts.insertLast(sphere.pos + dir * (sphere.radius - BorderThickness));
|
||||
}
|
||||
}
|
||||
else if(final.length == 2) {
|
||||
for(int s = 0; s < 2; ++s) {
|
||||
Sphere@ sphere = final[s];
|
||||
for(int i = 0; i < 24; ++i) {
|
||||
double a = double(i) * pi / 24.0 + (sphere.angle - pi * 0.5);
|
||||
vec3d dir(cos(a), 0, sin(a));
|
||||
verts.insertLast(sphere.pos + dir * sphere.radius);
|
||||
verts.insertLast(sphere.pos + dir * (sphere.radius - BorderThickness));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Sphere@ prev = final[final.length-1];
|
||||
for(int i = 0, cnt = final.length; i < cnt; ++i) {
|
||||
Sphere@ right = final[(i+1) % cnt];
|
||||
if(final[i].generateVerts(prev, right, verts))
|
||||
@prev = final[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
delta = false;
|
||||
|
||||
array<Sphere@> inner;
|
||||
|
||||
//Copy over all inner spheres
|
||||
for(int i = 0, cnt = spheres.length; i < cnt; ++i)
|
||||
if(!spheres[i].edge)
|
||||
inner.insertLast(spheres[i]);
|
||||
|
||||
rebuildPortion(spheres, edge_verts, edge_center);
|
||||
rebuildPortion(inner, inner_verts, inner_center);
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(delta)
|
||||
rebuild();
|
||||
return inner_verts.length != 0 && owner !is null;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(SHOW_TERRITORY_BORDERS) {
|
||||
vec2f innerUV(0,0), outerUV(1,0);
|
||||
vec2f innerUV_r(0,1), outerUV_r(1,1);
|
||||
if(owner is playerEmpire) {
|
||||
drawPolygonStart(edge_verts.length, material::Territory, Color(0xffffff80));
|
||||
for(int i = 0, cnt = edge_verts.length; i < cnt; i += 2) {
|
||||
drawPolygonPoint(edge_verts[i], outerUV);
|
||||
drawPolygonPoint(edge_verts[(i+2)%cnt], outerUV);
|
||||
drawPolygonPoint(edge_verts[(i+1)%cnt], innerUV);
|
||||
|
||||
drawPolygonPoint(edge_verts[(i+2)%cnt], outerUV);
|
||||
drawPolygonPoint(edge_verts[(i+3)%cnt], innerUV);
|
||||
drawPolygonPoint(edge_verts[(i+1)%cnt], innerUV);
|
||||
}
|
||||
drawPolygonEnd();
|
||||
}
|
||||
|
||||
double offset = 0;
|
||||
bool stipple = false;
|
||||
Empire@ master = owner.SubjugatedBy;
|
||||
if(master !is null) {
|
||||
stipple = true;
|
||||
master.color.toVec4(shader::STIPPLE_COLOR);
|
||||
}
|
||||
else {
|
||||
shader::STIPPLE_COLOR = vec4f(0.f, 0.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
drawPolygonStart(inner_verts.length, material::Territory, owner.color);
|
||||
for(int i = 0, cnt = inner_verts.length; i < cnt; i += 2) {
|
||||
if(stipple) {
|
||||
outerUV.y = offset;
|
||||
innerUV.y = offset;
|
||||
offset += inner_verts[i].distanceTo(inner_verts[(i+2)%cnt]);
|
||||
outerUV_r.y = offset;
|
||||
innerUV_r.y = offset;
|
||||
}
|
||||
|
||||
drawPolygonPoint(inner_verts[i], outerUV);
|
||||
drawPolygonPoint(inner_verts[(i+2)%cnt], outerUV_r);
|
||||
drawPolygonPoint(inner_verts[(i+1)%cnt], innerUV);
|
||||
|
||||
drawPolygonPoint(inner_verts[(i+2)%cnt], outerUV_r);
|
||||
drawPolygonPoint(inner_verts[(i+3)%cnt], innerUV_r);
|
||||
drawPolygonPoint(inner_verts[(i+1)%cnt], innerUV);
|
||||
}
|
||||
drawPolygonEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,308 @@
|
||||
import resources;
|
||||
import systems;
|
||||
|
||||
bool SHOW_TRADE_LINES = true;
|
||||
void setTradeLinesShown(bool enabled) {
|
||||
SHOW_TRADE_LINES = enabled;
|
||||
}
|
||||
|
||||
bool getTradeLinesShown() {
|
||||
return SHOW_TRADE_LINES;
|
||||
}
|
||||
|
||||
const Color LINE_START(0x00ffba80);
|
||||
const Color LINE_MIDDLE(0xaaaaaa80);
|
||||
const Color LINE_END(0x0000ff80);
|
||||
|
||||
const uint8 ALPHA_MIN = 80;
|
||||
const uint8 ALPHA_MAX = 160;
|
||||
const uint8 ALPHA_STEP = 10;
|
||||
|
||||
const double LOCAL_MAX_DIST = 12000.0;
|
||||
const double LOCAL_FADE_MIN = 600.0;
|
||||
const double LOCAL_FADE_MAX = 1000.0;
|
||||
|
||||
class TradeDesc {
|
||||
Object@ origin;
|
||||
int resId;
|
||||
Object@ destination;
|
||||
double originDist = 0.0, destDist = 0.0;
|
||||
const ResourceType@ type;
|
||||
};
|
||||
|
||||
class TradeLine {
|
||||
SystemDesc@ to;
|
||||
line3dd path;
|
||||
vec3d side;
|
||||
|
||||
int trades = 0;
|
||||
TradeDesc@[] gives;
|
||||
TradeDesc@[] takes;
|
||||
};
|
||||
|
||||
class TradeLinesNodeScript {
|
||||
SystemDesc@ system;
|
||||
TradeLine[] lines;
|
||||
TradeDesc@[] localTrades;
|
||||
int dispTrades = 0;
|
||||
|
||||
TradeLinesNodeScript(Node& node) {
|
||||
}
|
||||
|
||||
void establish(Node& node, uint sysId) {
|
||||
@system = getSystem(sysId);
|
||||
|
||||
uint adjCnt = system.adjacent.length;
|
||||
lines.length = adjCnt;
|
||||
double maxDist = system.radius * 2.0;
|
||||
for(uint i = 0; i < adjCnt; ++i) {
|
||||
TradeLine@ line = lines[i];
|
||||
@line.to = getSystem(system.adjacent[i]);
|
||||
|
||||
//Build the line verts
|
||||
line.path.start = system.position;
|
||||
line.path.end = line.to.position;
|
||||
|
||||
vec3d off = line.path.end - line.path.start;
|
||||
off.y = 0;
|
||||
off.normalize();
|
||||
|
||||
line.path.start += off * system.radius;
|
||||
line.path.end -= off * line.to.radius;
|
||||
|
||||
line.side = (quaterniond_fromAxisAngle(vec3d_up(), pi * 0.5) * off) * 32.0;
|
||||
line.path.start -= line.side;
|
||||
line.path.end -= line.side;
|
||||
|
||||
//Discover maximum distance
|
||||
double dist = line.path.length + system.radius + line.to.radius;
|
||||
if(dist > maxDist)
|
||||
maxDist = dist;
|
||||
}
|
||||
|
||||
//Set node position
|
||||
node.position = system.position;
|
||||
node.scale = maxDist;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
void addPathing(Node& node, int towards, Object@ origin, Object@ dest, int resId, uint resType) {
|
||||
if(towards < -1 || towards >= int(lines.length))
|
||||
return;
|
||||
TradeDesc trade;
|
||||
@trade.origin = origin;
|
||||
trade.originDist = origin.radius;
|
||||
Planet@ o = cast<Planet>(origin);
|
||||
if(o !is null)
|
||||
trade.originDist = o.OrbitSize;
|
||||
|
||||
@trade.destination = dest;
|
||||
trade.destDist = dest.radius;
|
||||
Planet@ d = cast<Planet>(dest);
|
||||
if(d !is null)
|
||||
trade.destDist = d.OrbitSize;
|
||||
trade.resId = resId;
|
||||
@trade.type = getResource(resType);
|
||||
|
||||
if(towards == -1) {
|
||||
localTrades.insertLast(trade);
|
||||
}
|
||||
else {
|
||||
TradeLine@ line = lines[towards];
|
||||
++line.trades;
|
||||
if(origin.region is system.object)
|
||||
line.gives.insertLast(trade);
|
||||
if(dest.region is line.to.object)
|
||||
line.takes.insertLast(trade);
|
||||
}
|
||||
|
||||
++dispTrades;
|
||||
node.visible = true;
|
||||
}
|
||||
|
||||
void removePathing(Node& node, int towards, Object@ origin, int resId) {
|
||||
if(towards < -1 || towards >= int(lines.length))
|
||||
return;
|
||||
if(towards == -1) {
|
||||
uint cnt = localTrades.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
TradeDesc@ trade = localTrades[i];
|
||||
if(trade.origin is origin && trade.resId == resId) {
|
||||
localTrades.removeAt(i);
|
||||
--dispTrades;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
TradeLine@ line = lines[towards];
|
||||
--line.trades;
|
||||
--dispTrades;
|
||||
|
||||
uint cnt = line.gives.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
TradeDesc@ trade = line.gives[i];
|
||||
if(trade.origin is origin && trade.resId == resId) {
|
||||
line.gives.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
cnt = line.takes.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
TradeDesc@ trade = line.takes[i];
|
||||
if(trade.origin is origin && trade.resId == resId) {
|
||||
line.takes.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.visible = dispTrades != 0;
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
if(system !is null && lines.length != system.adjacent.length)
|
||||
establish(node, system.index);
|
||||
return system !is null && SHOW_TRADE_LINES;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(system is null) // Why can this ever be true?
|
||||
return;
|
||||
|
||||
bool sysVisible = system.object.VisionMask & playerEmpire.visionMask != 0;
|
||||
float localAlpha = 1.f;
|
||||
|
||||
//Draw local trades
|
||||
/*if(node.sortDistance < LOCAL_MAX_DIST && sysVisible) {
|
||||
//Calculate distance to plane
|
||||
line3dd camLine(cameraPos, cameraPos+cameraFacing);
|
||||
vec3d intersect;
|
||||
double planeDist;
|
||||
if(!camLine.intersectY(intersect, node.position.y, false)) {
|
||||
intersect = cameraPos;
|
||||
intersect.y = node.position.y;
|
||||
planeDist = sqrt(
|
||||
sqr(max(0.0, intersect.distanceTo(node.position) - system.radius))
|
||||
+ sqr(cameraPos.y - node.position.y));
|
||||
}
|
||||
else {
|
||||
planeDist = intersect.distanceTo(cameraPos);
|
||||
max(0.0, intersect.distanceTo(node.position) - system.radius);
|
||||
}
|
||||
|
||||
Color startColor = LINE_START;
|
||||
Color endColor = LINE_END;
|
||||
|
||||
//Fade lines
|
||||
localAlpha = clamp((planeDist - LOCAL_FADE_MIN) / (LOCAL_FADE_MAX - LOCAL_FADE_MIN), 0.0, 1.0);
|
||||
startColor.a = localAlpha * 180;
|
||||
endColor.a = startColor.a;
|
||||
|
||||
if(startColor.a != 0) {
|
||||
uint cnt = localTrades.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
TradeDesc@ trade = localTrades[i];
|
||||
vec3d from = trade.origin.position;
|
||||
vec3d to = trade.destination.position;
|
||||
|
||||
drawLine(from + (to - from).normalized(trade.originDist), to + (from - to).normalized(trade.destDist),
|
||||
startColor, endColor, 0.0);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//Draw paths
|
||||
uint cnt = lines.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
TradeLine@ line = lines[i];
|
||||
if(line.trades > 0)
|
||||
drawPath(line, sysVisible, localAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
void drawPath(TradeLine@ line, bool sysVisible, float localAlpha) {
|
||||
bool otherVisible = line.to.object.VisionMask & playerEmpire.visionMask != 0;
|
||||
double startDist = 0.0;
|
||||
if(line.gives.length != 0)
|
||||
startDist = line.gives[0].origin.position.distanceTo(line.path.start);
|
||||
double dist = line.path.length + startDist;
|
||||
vec2f startDistLeft(startDist, 0), startDistRight(startDist, 1);
|
||||
vec2f endDistLeft(dist, 0), endDistRight(dist, 1);
|
||||
|
||||
//Draw the line
|
||||
if(sysVisible || otherVisible) {
|
||||
Color color = LINE_MIDDLE;
|
||||
color.a = min(ALPHA_MAX, ALPHA_MIN + ALPHA_STEP * line.trades);
|
||||
|
||||
drawPolygonStart(2, material::TradePaths, color);
|
||||
drawPolygonPoint(line.path.start + line.side, startDistLeft);
|
||||
drawPolygonPoint(line.path.end + line.side, endDistLeft);
|
||||
drawPolygonPoint(line.path.start - line.side, startDistRight);
|
||||
|
||||
drawPolygonPoint(line.path.end + line.side, endDistLeft);
|
||||
drawPolygonPoint(line.path.end - line.side, endDistRight);
|
||||
drawPolygonPoint(line.path.start - line.side, startDistRight);
|
||||
drawPolygonEnd();
|
||||
}
|
||||
|
||||
//Draw taking
|
||||
/*if(otherVisible) {
|
||||
Color midCol = LINE_MIDDLE;
|
||||
Color endCol = LINE_END;
|
||||
|
||||
midCol.a = localAlpha * 180;
|
||||
endCol.a = midCol.a;
|
||||
|
||||
uint cnt = line.takes.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
auto@ dest = line.takes[i];
|
||||
vec3d to = dest.destination.position;
|
||||
vec3d from = line.path.end;
|
||||
|
||||
drawLine(from, to + (from - to).normalized(dest.destDist), midCol, endCol, dist);
|
||||
}
|
||||
}*/
|
||||
|
||||
//Draw giving
|
||||
/*if(sysVisible) {
|
||||
Color startCol = LINE_START;
|
||||
Color midCol = LINE_MIDDLE;
|
||||
|
||||
startCol.a = localAlpha * 255;
|
||||
midCol.a = startCol.a;
|
||||
|
||||
uint cnt = line.gives.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
auto@ dest = line.gives[i];
|
||||
vec3d from = dest.origin.position;
|
||||
vec3d to = line.path.start;
|
||||
|
||||
drawLine(from + (to - from).normalized(dest.originDist), to, startCol, midCol, 0.0);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void drawLine(const vec3d& from, const vec3d& to, const Color& fromColor, const Color& toColor, double len) {
|
||||
line3dd path(from, to);
|
||||
vec2f startLeft(len, 0), startRight(len, 1);
|
||||
len += path.length;
|
||||
vec2f endLeft(len, 0), endRight(len, 1);
|
||||
|
||||
vec3d off = path.end - path.start;
|
||||
off.y = 0;
|
||||
off.normalize();
|
||||
|
||||
vec3d side = (quaterniond_fromAxisAngle(vec3d_up(), pi * 0.5) * off) * 10.0;
|
||||
|
||||
drawPolygonStart(2, material::TradePaths);
|
||||
drawPolygonPoint(path.start + side, startLeft, fromColor);
|
||||
drawPolygonPoint(path.end + side, endLeft, toColor);
|
||||
drawPolygonPoint(path.start - side, startRight, fromColor);
|
||||
|
||||
drawPolygonPoint(path.end + side, endLeft, toColor);
|
||||
drawPolygonPoint(path.end - side, endRight, toColor);
|
||||
drawPolygonPoint(path.start - side, startRight, fromColor);
|
||||
drawPolygonEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#priority render 20
|
||||
const double GROW_TIMER = 1.0;
|
||||
const double SHOW_TIMER = 10.0;
|
||||
const double SHOW_SCALE = 0.1;
|
||||
|
||||
class Ping {
|
||||
Empire@ fromEmpire;
|
||||
uint type = 0;
|
||||
double timer = 0.0;
|
||||
|
||||
Ping(Node& node) {
|
||||
}
|
||||
|
||||
void establish(Node& node, Empire@ emp, uint t) {
|
||||
@fromEmpire = emp;
|
||||
type = t;
|
||||
node.rebuildTransform();
|
||||
}
|
||||
|
||||
bool preRender(Node& node) {
|
||||
timer += frameLength;
|
||||
if(timer >= SHOW_TIMER)
|
||||
node.visible = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void render(Node& node) {
|
||||
if(fromEmpire is null)
|
||||
return;
|
||||
double scale = 1.0;
|
||||
|
||||
double t = timer % GROW_TIMER;
|
||||
if(timer > GROW_TIMER)
|
||||
scale *= 0.25;
|
||||
if(t < GROW_TIMER*0.5)
|
||||
scale *= t/(GROW_TIMER*0.5);
|
||||
else
|
||||
scale *= (1.0 - (t-GROW_TIMER*0.5)/(GROW_TIMER*0.5)) * (1.0 - SHOW_SCALE) + SHOW_SCALE;
|
||||
scale *= node.sortDistance * 0.2;
|
||||
|
||||
renderPlane(material::Ping, node.abs_position, scale, fromEmpire.color);
|
||||
if(type == 0)
|
||||
renderBillboard(spritesheet::AttributeIcons, 5, node.abs_position, node.sortDistance * 0.02, 0, fromEmpire.color);
|
||||
else if(type == 1)
|
||||
renderBillboard(material::Minus, node.abs_position, node.sortDistance * 0.02, 0);
|
||||
}
|
||||
};
|
||||
|
||||
Mutex mtx;
|
||||
array<PingNode@> pings;
|
||||
array<Empire@> pingEmpires;
|
||||
|
||||
void showPing(Empire@ fromEmpire, vec3d position, uint type = 0) {
|
||||
//Make new ping
|
||||
PingNode@ p = PingNode();
|
||||
p.position = position;
|
||||
p.scale = 10000.0;
|
||||
p.establish(fromEmpire, type);
|
||||
|
||||
{
|
||||
Sound@ snd;
|
||||
if(type == 0)
|
||||
@snd = sound::ping.play(position, pause=true, priority=true);
|
||||
else
|
||||
@snd = sound::ping_warn.play(position, pause=true, priority=true);
|
||||
if(snd !is null) {
|
||||
snd.rolloff = 0;
|
||||
snd.resume();
|
||||
}
|
||||
}
|
||||
|
||||
Lock lock(mtx);
|
||||
pings.insertLast(p);
|
||||
pingEmpires.insertLast(fromEmpire);
|
||||
|
||||
//Allow at most 5 pings at the same time per empire
|
||||
uint count = 0;
|
||||
for(int i = pings.length - 1; i >= 0; --i) {
|
||||
if(pingEmpires[i] !is fromEmpire)
|
||||
continue;
|
||||
|
||||
++count;
|
||||
if(count > 5) {
|
||||
pings[i].markForDeletion();
|
||||
pings.removeAt(i);
|
||||
pingEmpires.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
Lock lock(mtx);
|
||||
for(int i = pings.length - 1; i >= 0; --i) {
|
||||
if(!pings[i].visible) {
|
||||
pings.removeAt(i);
|
||||
pingEmpires.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import elements.BaseGuiElement;
|
||||
import elements.GuiOverlay;
|
||||
import elements.GuiText;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiSkinElement;
|
||||
|
||||
QueueResponse@ response;
|
||||
|
||||
class QueueResponse : GuiOverlay {
|
||||
GuiSkinElement@ bg;
|
||||
GuiText@ state;
|
||||
GuiButton@ accept, reject;
|
||||
double speed = 1;
|
||||
|
||||
QueueResponse() {
|
||||
speed = gameSpeed;
|
||||
gameSpeed = 0;
|
||||
|
||||
super(null);
|
||||
@bg = GuiSkinElement(this, Alignment(Left+0.5f-250,Top+0.5f-100,Width=500,Height=200), SS_Dialog);
|
||||
|
||||
@state = GuiText(bg, Alignment(Left+10,Top+20,Right-10,Height=28), locale::MP_QUEUE_READY);
|
||||
state.font = FT_Medium;
|
||||
state.horizAlign = 0.5;
|
||||
|
||||
@accept = GuiButton(bg, Alignment(
|
||||
Left+0.5f-205, Top+65, Width=200, Height=46),
|
||||
locale::MP_QUEUE_ACCEPT);
|
||||
accept.font = FT_Medium;
|
||||
accept.color = Color(0x88ff88ff);
|
||||
|
||||
@reject = GuiButton(bg, Alignment(
|
||||
Left+0.5f+5, Top+65, Width=200, Height=46),
|
||||
locale::MP_QUEUE_REJECT);
|
||||
reject.font = FT_Medium;
|
||||
reject.color = Color(0xff8888ff);
|
||||
|
||||
updateAbsolutePosition();
|
||||
update();
|
||||
bringToFront();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
switch(event.type) {
|
||||
case GUI_Clicked:
|
||||
if(event.caller is accept) {
|
||||
if(game_running)
|
||||
saveGame(path_join(baseProfile["saves"], "queue_accepted.sr2"));
|
||||
cloud::acceptQueue();
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is reject) {
|
||||
cloud::rejectQueue();
|
||||
remove();
|
||||
gameSpeed = speed;
|
||||
@response = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void close() {
|
||||
//Absorb the various close events that can occur
|
||||
}
|
||||
|
||||
void update() {
|
||||
uint ready = 0, players = 0;
|
||||
|
||||
if(cloud::queueRequest)
|
||||
state.text = locale::MP_QUEUE_READY;
|
||||
else if(cloud::getQueuePlayerWait(ready, players)) {
|
||||
state.text = format(locale::MP_QUEUE_WAITING, ready, players);
|
||||
accept.visible = false;
|
||||
reject.visible = false;
|
||||
}
|
||||
else {
|
||||
gameSpeed = speed;
|
||||
remove();
|
||||
@response = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void tick(double time) {
|
||||
if(response is null && cloud::queueRequest)
|
||||
@response = QueueResponse();
|
||||
else if(response !is null)
|
||||
response.update();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
void init() {
|
||||
setSkybox(getMaterial("Skybox"));
|
||||
setSkyboxMesh(getModel("Skybox"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
const double[] SPEED_STEPS = {0.1, 0.2, 0.5, 0.8, 1.0, 1.2, 1.5, 2.0, 5.0, 10.0};
|
||||
|
||||
bool PAUSED = false;
|
||||
double PREV_SPEED = 1.0;
|
||||
|
||||
uint closest_step(double speed) {
|
||||
double diff = INFINITY;
|
||||
uint index = 0;
|
||||
for(uint i = 0, cnt = SPEED_STEPS.length; i < cnt; ++i) {
|
||||
double d = abs(SPEED_STEPS[i] - speed);
|
||||
if(d < diff) {
|
||||
diff = d;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void pause(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
if(PAUSED) {
|
||||
PAUSED = false;
|
||||
setGameSpeed(PREV_SPEED);
|
||||
}
|
||||
else {
|
||||
PAUSED = true;
|
||||
PREV_SPEED = gameSpeed;
|
||||
setGameSpeed(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void speed_default(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
PAUSED = false;
|
||||
setGameSpeed(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void speed_faster(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
uint index = closest_step(PAUSED ? PREV_SPEED : gameSpeed);
|
||||
PAUSED = false;
|
||||
double newSpeed = SPEED_STEPS[min(index + 1, SPEED_STEPS.length-1)];
|
||||
setGameSpeed(newSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void speed_fastest(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
PAUSED = false;
|
||||
double newSpeed = SPEED_STEPS[SPEED_STEPS.length-1];
|
||||
setGameSpeed(newSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void speed_slower(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
uint index = closest_step(PAUSED ? PREV_SPEED : gameSpeed);
|
||||
PAUSED = false;
|
||||
double newSpeed = SPEED_STEPS[max(index, 1) - 1];
|
||||
setGameSpeed(newSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void speed_slowest(bool pressed = false) {
|
||||
if(!pressed) {
|
||||
PAUSED = false;
|
||||
double newSpeed = SPEED_STEPS[0];
|
||||
setGameSpeed(newSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
class GameSpeed : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
double value = toDouble(args);
|
||||
PAUSED = value == 0;
|
||||
if(PAUSED)
|
||||
PREV_SPEED = gameSpeed;
|
||||
setGameSpeed(value);
|
||||
}
|
||||
};
|
||||
|
||||
class Pause : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
pause(false);
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("game_speed", GameSpeed());
|
||||
addConsoleCommand("pause", Pause());
|
||||
|
||||
keybinds::Global.addBind(KB_PAUSE, "pause");
|
||||
keybinds::Global.addBind(KB_SPEED_SLOWER, "speed_slower");
|
||||
keybinds::Global.addBind(KB_SPEED_DEFAULT, "speed_default");
|
||||
keybinds::Global.addBind(KB_SPEED_FASTER, "speed_faster");
|
||||
}
|
||||
Reference in New Issue
Block a user