Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+135
View File
@@ -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();
}
+357
View File
@@ -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());
}
+554
View File
@@ -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;
}
+132
View File
@@ -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());
}
+144
View File
@@ -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);
}
+585
View File
@@ -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));
}
+349
View File
@@ -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));
}
};
+40
View File
@@ -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);
}
}
};
+155
View File
@@ -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);
}
}
};
+109
View File
@@ -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);
}
}
};
+63
View File
@@ -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();
}
};
+44
View File
@@ -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();
}
};
+63
View File
@@ -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);
}
}
};
+85
View File
@@ -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) {
//}
};
+375
View File
@@ -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);
}
}
};
+346
View File
@@ -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;
}
+30
View File
@@ -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();
}
};
+58
View File
@@ -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);
}
}
};
+226
View File
@@ -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);
}
};
+445
View File
@@ -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();
}
}
};
+351
View File
@@ -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();
}
}
};
+308
View File
@@ -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();
}
}
+100
View File
@@ -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);
}
}
}
+91
View File
@@ -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();
}
+5
View File
@@ -0,0 +1,5 @@
void init() {
setSkybox(getMaterial("Skybox"));
setSkyboxMesh(getModel("Skybox"));
}
+98
View File
@@ -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");
}