2017-02-09 00:00:35 +00:00
|
|
|
module TS.SpaceTac {
|
2017-04-25 22:24:52 +00:00
|
|
|
/**
|
|
|
|
* Main game universe
|
|
|
|
*/
|
2017-02-07 18:54:53 +00:00
|
|
|
export class Universe {
|
2015-03-19 00:00:00 +00:00
|
|
|
// List of star systems
|
2017-04-25 22:24:52 +00:00
|
|
|
stars: Star[] = []
|
2015-03-19 00:00:00 +00:00
|
|
|
|
|
|
|
// List of links between star systems
|
2017-04-25 22:24:52 +00:00
|
|
|
starlinks: StarLink[] = []
|
2015-03-19 00:00:00 +00:00
|
|
|
|
|
|
|
// Radius of the universe
|
2017-04-25 22:24:52 +00:00
|
|
|
radius = 5
|
2015-03-19 00:00:00 +00:00
|
|
|
|
2017-04-25 22:24:52 +00:00
|
|
|
// Source of randomness
|
|
|
|
random = RandomGenerator.global;
|
2015-03-19 00:00:00 +00:00
|
|
|
|
|
|
|
// Generates a universe, with star systems and such
|
2017-04-25 22:24:52 +00:00
|
|
|
generate(starcount = 50): void {
|
2017-05-18 20:32:04 +00:00
|
|
|
while (this.stars.length == 0 || any(this.stars, star => star.getLinks().length == 0)) {
|
|
|
|
this.stars = this.generateStars(starcount);
|
2015-03-19 00:00:00 +00:00
|
|
|
|
2017-05-18 20:32:04 +00:00
|
|
|
let links = this.getPotentialLinks();
|
|
|
|
this.starlinks = this.filterCrossingLinks(links);
|
|
|
|
}
|
2015-03-24 00:00:00 +00:00
|
|
|
|
2017-04-25 22:24:52 +00:00
|
|
|
this.generateWarpLocations();
|
2017-01-26 23:01:04 +00:00
|
|
|
|
2015-03-24 00:00:00 +00:00
|
|
|
this.stars.forEach((star: Star) => {
|
2017-04-25 22:24:52 +00:00
|
|
|
star.generate();
|
2015-03-24 00:00:00 +00:00
|
|
|
});
|
2017-04-25 22:24:52 +00:00
|
|
|
|
|
|
|
this.setEncounterLevels();
|
2017-05-10 15:29:10 +00:00
|
|
|
|
|
|
|
this.addShops();
|
2015-03-19 00:00:00 +00:00
|
|
|
}
|
2015-03-19 00:00:00 +00:00
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
// Generate a given number of stars, not too crowded
|
2017-04-25 22:24:52 +00:00
|
|
|
generateStars(count: number): Star[] {
|
2015-03-19 00:00:00 +00:00
|
|
|
var result: Star[] = [];
|
|
|
|
|
2015-03-25 00:00:00 +00:00
|
|
|
var names = new NameGenerator(Star.NAMES_POOL);
|
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
while (count) {
|
2017-04-25 22:24:52 +00:00
|
|
|
var x = this.random.random() * this.radius * 2.0 - this.radius;
|
|
|
|
var y = this.random.random() * this.radius * 2.0 - this.radius;
|
2015-03-19 00:00:00 +00:00
|
|
|
var star = new Star(this, x, y);
|
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
var nearest = this.getNearestTo(star, result);
|
2015-03-19 00:00:00 +00:00
|
|
|
if (nearest && nearest.getDistanceTo(star) < this.radius * 0.1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-09 17:11:00 +00:00
|
|
|
star.name = names.getName() || "Star";
|
2015-03-19 00:00:00 +00:00
|
|
|
result.push(star);
|
2015-03-19 00:00:00 +00:00
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
count--;
|
2015-03-19 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get a list of potential links between the stars
|
|
|
|
getPotentialLinks(): StarLink[] {
|
|
|
|
var result: StarLink[] = [];
|
|
|
|
|
|
|
|
this.stars.forEach((first: Star, idx1: number) => {
|
|
|
|
this.stars.forEach((second: Star, idx2: number) => {
|
|
|
|
if (idx1 < idx2) {
|
|
|
|
if (first.getDistanceTo(second) < this.radius * 0.6) {
|
|
|
|
result.push(new StarLink(first, second));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter a list of potential links to avoid crossing ones
|
|
|
|
filterCrossingLinks(links: StarLink[]): StarLink[] {
|
2017-01-26 23:01:04 +00:00
|
|
|
var result: StarLink[] = [];
|
2015-03-19 00:00:00 +00:00
|
|
|
|
|
|
|
links.forEach((link1: StarLink) => {
|
|
|
|
var crossed = false;
|
|
|
|
links.forEach((link2: StarLink) => {
|
|
|
|
if (link1 !== link2 && link1.isCrossing(link2) && link1.getLength() >= link2.getLength()) {
|
|
|
|
crossed = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!crossed) {
|
|
|
|
result.push(link1);
|
2015-03-19 00:00:00 +00:00
|
|
|
}
|
|
|
|
});
|
2015-03-19 00:00:00 +00:00
|
|
|
|
|
|
|
return result;
|
2015-03-19 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 23:01:04 +00:00
|
|
|
// Generate warp locations for the links between stars
|
2017-04-25 22:24:52 +00:00
|
|
|
generateWarpLocations() {
|
2017-01-26 23:01:04 +00:00
|
|
|
this.starlinks.forEach(link => {
|
2017-04-25 22:24:52 +00:00
|
|
|
let warp1 = link.first.generateWarpLocationTo(link.second, this.random);
|
|
|
|
let warp2 = link.second.generateWarpLocationTo(link.first, this.random);
|
2017-01-26 23:01:04 +00:00
|
|
|
|
|
|
|
warp1.setJumpDestination(warp2);
|
|
|
|
warp2.setJumpDestination(warp1);
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-19 00:00:00 +00:00
|
|
|
// Get the star nearest to another
|
2017-03-09 17:11:00 +00:00
|
|
|
getNearestTo(star: Star, others = this.stars): Star | null {
|
2015-03-19 00:00:00 +00:00
|
|
|
if (others.length === 0) {
|
2015-03-19 00:00:00 +00:00
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
var mindist = this.radius * 2.0;
|
2017-03-09 17:11:00 +00:00
|
|
|
var nearest: Star | null = null;
|
2015-03-19 00:00:00 +00:00
|
|
|
others.forEach((istar: Star) => {
|
2015-03-19 00:00:00 +00:00
|
|
|
if (istar !== star) {
|
2017-01-26 23:01:04 +00:00
|
|
|
var dist = star.getDistanceTo(istar);
|
2015-03-19 00:00:00 +00:00
|
|
|
if (dist < mindist) {
|
|
|
|
nearest = istar;
|
|
|
|
mindist = dist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return nearest;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if a link exists between two stars
|
|
|
|
areLinked(first: Star, second: Star): boolean {
|
|
|
|
var result = false;
|
|
|
|
this.starlinks.forEach((link: StarLink) => {
|
|
|
|
if (link.isLinking(first, second)) {
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
2015-03-25 00:00:00 +00:00
|
|
|
|
|
|
|
// Add a link between two stars
|
|
|
|
addLink(first: Star, second: Star): void {
|
|
|
|
if (!this.areLinked(first, second)) {
|
|
|
|
this.starlinks.push(new StarLink(first, second));
|
|
|
|
}
|
|
|
|
}
|
2017-04-25 22:24:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the average level of encounters in each system
|
|
|
|
*/
|
|
|
|
setEncounterLevels(maximal?: number) {
|
|
|
|
if (!maximal) {
|
|
|
|
maximal = Math.min(99, Math.ceil(Math.sqrt(this.stars.length)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset levels
|
|
|
|
this.stars.forEach(star => star.level = 1);
|
|
|
|
|
|
|
|
// Choose two systems to be the lowest and highest danger zones (not connected directly)
|
|
|
|
let lowest = this.random.choice(this.stars.filter(star => star.getLinks().length > 1));
|
|
|
|
let highest = this.random.choice(this.stars.filter(star => star != lowest && !star.getLinkTo(lowest)));
|
|
|
|
highest.level = maximal;
|
|
|
|
|
|
|
|
// Make danger gradients
|
|
|
|
range(this.stars.length).forEach(() => {
|
|
|
|
this.stars.forEach(star => {
|
|
|
|
if (star != lowest && star != highest) {
|
|
|
|
let neighbors = star.getLinks().map(link => nn(link.getPeer(star)));
|
|
|
|
let minlevel = min(neighbors.map(neighbor => neighbor.level));
|
|
|
|
let maxlevel = max(neighbors.map(neighbor => neighbor.level));
|
|
|
|
star.level = (minlevel + maxlevel) / 2;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Round levels
|
|
|
|
this.stars.forEach(star => star.level = Math.round(star.level));
|
|
|
|
}
|
|
|
|
|
2017-05-10 15:29:10 +00:00
|
|
|
/**
|
|
|
|
* Add random shops
|
|
|
|
*/
|
|
|
|
addShops(): void {
|
|
|
|
this.stars.forEach(star => {
|
|
|
|
star.locations.forEach(location => {
|
|
|
|
if (this.random.random() > 0.6) {
|
|
|
|
location.addShop(star.level);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-04-25 22:24:52 +00:00
|
|
|
/**
|
|
|
|
* Get a good start location
|
|
|
|
*/
|
|
|
|
getStartLocation(): StarLocation {
|
|
|
|
let stars = acopy(this.stars);
|
|
|
|
stars.sort((a, b) => cmp(a.level, b.level));
|
|
|
|
return stars[0].locations[0];
|
|
|
|
}
|
2014-12-29 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|