001package com.fs.starfarer.api.impl.campaign.fleets; 002 003import java.util.List; 004 005import org.lwjgl.util.vector.Vector2f; 006 007import com.fs.starfarer.api.Global; 008import com.fs.starfarer.api.campaign.CampaignFleetAPI; 009import com.fs.starfarer.api.campaign.FactionAPI; 010import com.fs.starfarer.api.campaign.JumpPointAPI; 011import com.fs.starfarer.api.campaign.LocationAPI; 012import com.fs.starfarer.api.campaign.SectorEntityToken; 013import com.fs.starfarer.api.campaign.StarSystemAPI; 014import com.fs.starfarer.api.campaign.econ.MarketAPI; 015import com.fs.starfarer.api.impl.campaign.JumpPointInteractionDialogPluginImpl; 016import com.fs.starfarer.api.impl.campaign.shared.WormholeManager; 017import com.fs.starfarer.api.util.Misc; 018 019public class RouteLocationCalculator { 020 021 022 public static enum IntervalType { 023 DAYS, 024 TRAVEL_TIME, 025 FRACTION_OF_REMAINING 026 } 027 028 public static class TaskInterval { 029 public IntervalType type; 030 public float value; 031 032 public TaskInterval(IntervalType type, float value) { 033 this.type = type; 034 this.value = value; 035 } 036 037 public TaskInterval(IntervalType type) { 038 super(); 039 this.type = type; 040 } 041 042 043 public static TaskInterval days(float days) { 044 return new TaskInterval(IntervalType.DAYS, days); 045 } 046 public static TaskInterval travel() { 047 return new TaskInterval(IntervalType.TRAVEL_TIME, 0f); 048 } 049 public static TaskInterval remaining(float fraction) { 050 return new TaskInterval(IntervalType.FRACTION_OF_REMAINING, fraction); 051 } 052 } 053 054 public static float getTravelDays(SectorEntityToken from, SectorEntityToken to) { 055 float dist = 0f; 056 if (from.getContainingLocation() != to.getContainingLocation()) { 057 dist = Misc.getDistance(from.getLocationInHyperspace(), to.getLocationInHyperspace()); 058 if (!from.isInHyperspace()) { 059 dist += 5000; // random guess at distance to/from jump-point 060 } 061 if (!to.isInHyperspace()) { 062 dist += 5000; // random guess at distance to/from jump-point 063 } 064 } else { 065 dist = Misc.getDistance(from, to); 066 if (from.isSystemCenter() || to.isSystemCenter()) dist = 0f; 067 } 068 069 070 float travelTime = dist / 1500f; 071 return travelTime; 072 } 073 074 public static void computeIntervalsAndSetLocation(CampaignFleetAPI fleet, float daysElapsed, float maxDays, 075 boolean onlyComputeIntervals, 076 TaskInterval [] intervals, 077 SectorEntityToken ... sequence) { 078 079 float totalDays = 0f; 080 float totalFraction = 0f; 081 for (int i = 0; i < intervals.length; i++) { 082 TaskInterval t = intervals[i]; 083 if (t.type == IntervalType.TRAVEL_TIME) { 084 SectorEntityToken from = sequence[i]; 085 SectorEntityToken to = sequence[i + 1]; 086 087 float travelTime = getTravelDays(from, to); 088 t.value = travelTime; 089 } 090 091 if (t.type == IntervalType.FRACTION_OF_REMAINING) { 092 totalFraction += t.value; 093 } else { 094 totalDays += t.value; 095 } 096 } 097 098 if (totalFraction > 0) { 099 float remaining = maxDays - totalDays; 100 for (TaskInterval t : intervals) { 101 if (t.type == IntervalType.FRACTION_OF_REMAINING) { 102 t.value = Math.max(0.1f, t.value / totalFraction * remaining); 103 } 104 } 105 } 106 107 totalDays = 0f; 108 for (TaskInterval t : intervals) { 109 totalDays += t.value; 110 } 111 112 if (totalDays > maxDays) { 113 for (TaskInterval t : intervals) { 114 t.value *= maxDays / totalDays; 115 } 116 } 117 118 float soFar = 0f; 119 float progress = 0f; 120 int index = 0; 121 SectorEntityToken from = null; 122 SectorEntityToken to = null; 123 //for (float curr : intervals) { 124 for (int i = 0; i < intervals.length; i++) { 125 float curr = intervals[i].value; 126 if (curr < 0) curr = 0; 127 128 if (soFar + curr > daysElapsed && curr > 0) { 129 progress = (daysElapsed - soFar) / curr; 130 if (progress < 0) progress = 0; 131 if (progress > 1) progress = 1; 132 133 from = sequence[index]; 134 to = sequence[index + 1]; 135 intervals[i].value *= (1f - progress); 136 break; 137 } 138 soFar += curr; 139 intervals[i].value = 0; 140 index++; 141 } 142 if (from == null) { 143 index = intervals.length - 1; 144 from = sequence[sequence.length - 2]; 145 to = sequence[sequence.length - 1]; 146 progress = 1f; 147 } 148 149 if (onlyComputeIntervals) { 150 return; 151 } 152 153 setLocation(fleet, progress, from, to); 154 } 155 156 157 /** 158 * Used to assign a reasonable location to a fleet that was just spawned by RouteManager. 159 * 160 * Will normalize the intervals array to not exceed maxDays total, and will then set 161 * its values to the days remaining for each section. 162 * 163 * 164 * @param fleet 165 * @param daysElapsed 166 * @param intervals 167 * @param sequence Must have length = intervals.length + 1. 168 */ 169 public static int setLocation(CampaignFleetAPI fleet, 170 float daysElapsed, float maxDays, int overflowIndex, 171 boolean onlyAdjustIntervals, 172 float [] intervals, SectorEntityToken ... sequence) { 173 174 float total = 0f; 175 for (float curr : intervals) { 176 total += curr; 177 } 178 179 // either add/subtract any extra days to the overflow interval, or scale all if there isn't one 180 // e.g. treat intervals as weights if there is no overflow interval specified 181 if (total != maxDays) { 182 if (overflowIndex >= 0) { 183 float extra = total - maxDays; 184 intervals[overflowIndex] -= extra; 185 if (intervals[overflowIndex] <= 0) { 186 total = maxDays - intervals[overflowIndex]; 187 intervals[overflowIndex] = 0; 188 for (int i = 0; i < intervals.length; i++) { 189 intervals[i] *= maxDays / total; 190 } 191 } 192 } else { 193 for (int i = 0; i < intervals.length; i++) { 194 intervals[i] *= maxDays / total; 195 } 196 } 197 } 198 199 200 float soFar = 0f; 201 float progress = 0f; 202 int index = 0; 203 SectorEntityToken from = null; 204 SectorEntityToken to = null; 205 //for (float curr : intervals) { 206 for (int i = 0; i < intervals.length; i++) { 207 float curr = intervals[i]; 208 if (curr < 0) curr = 0; 209 210 if (soFar + curr > daysElapsed && curr > 0) { 211 progress = (daysElapsed - soFar) / curr; 212 if (progress < 0) progress = 0; 213 if (progress > 1) progress = 1; 214 215 from = sequence[index]; 216 to = sequence[index + 1]; 217 intervals[i] *= (1f - progress); 218 break; 219 } 220 soFar += curr; 221 intervals[i] = 0; 222 index++; 223 } 224 if (from == null) { 225 index = intervals.length - 1; 226 from = sequence[sequence.length - 2]; 227 to = sequence[sequence.length - 1]; 228 progress = 1f; 229 } 230 231 232 if (onlyAdjustIntervals) { 233 return index; 234 } 235 236 setLocation(fleet, progress, from, to); 237 238 return index; 239 } 240 241 public static void setLocation(CampaignFleetAPI fleet, 242 float progress, SectorEntityToken from, SectorEntityToken to) { 243// Cases to handle: 244// from/to are same 245// hyper - hyper 246// system - system 247// system - hyper 248// hyper - system 249// system - other_system 250 251// Subcase: 252// one end is system center - spawn "somewhere in system" in that case 253 254 if (progress < 0) progress = 0; 255 if (progress > 1) progress = 1; 256 257 if (to == null) to = from; 258 if (from == null) from = to; 259 260 if (from == null && to == null) return; 261 262 float varianceMult = getVarianceMult(progress); 263 264 SectorEntityToken forSystemCenterCheck = null; 265 LocationAPI conLoc = null; 266 Vector2f loc = null; 267 if (from == to) { 268 conLoc = from.getContainingLocation(); 269 forSystemCenterCheck = from; 270 271 if (!conLoc.isHyperspace()) { 272 if (progress > 0.03f) { 273 // at a typical orbiting radius 274 loc = Misc.getPointAtRadius(from.getLocation(), from.getRadius() + 100f + (float) Math.random() * 100f); 275 } else { 276 loc = new Vector2f(from.getLocation()); 277 } 278 } else { 279 loc = Misc.getPointWithinRadius(from.getLocation(), 100f + 900f * varianceMult); 280 } 281 } 282 // hyper to hyper 283 else if (from.isInHyperspace() && to.isInHyperspace()) { 284 conLoc = from.getContainingLocation(); 285 loc = Misc.interpolateVector(from.getLocation(), 286 to.getLocation(), 287 progress); 288 loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult); 289 } 290 // different locations in same system (not hyper) 291 else if (from.getContainingLocation() == to.getContainingLocation()) { 292 conLoc = from.getContainingLocation(); 293 if (from.isSystemCenter()) forSystemCenterCheck = from; 294 if (to.isSystemCenter()) forSystemCenterCheck = to; 295 296 loc = Misc.interpolateVector(from.getLocation(), 297 to.getLocation(), 298 progress); 299 if (conLoc instanceof StarSystemAPI && 300 Misc.getDistance(loc, new Vector2f()) < 2000) { // quick hack to avoid primary star 301 loc = Misc.getPointAtRadius(new Vector2f(), 2000f); 302 } else { 303 loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult); 304 } 305 } 306 // one in hyper, one isn't 307 else if (from.isInHyperspace() != to.isInHyperspace()) { 308 SectorEntityToken inSystem = from; 309 SectorEntityToken inHyper = to; 310 float p = progress; 311 if (from.isInHyperspace()) { 312 inSystem = to; 313 inHyper = from; 314 p = 1f - progress; 315 } 316 317 JumpPointAPI jp = findJumpPointToUse(fleet, inSystem); 318 if (jp == null) return; 319 float d1 = Misc.getDistance(inSystem, jp); 320 float d2 = Misc.getDistance(jp.getLocationInHyperspace(), inHyper.getLocation()); 321 if (d1 < 1) d1 = 1; 322 if (d2 < 1) d2 = 1; 323 324 float t = d1 / (d1 + d2); 325 if (p < t) { // in system on way to jump-point 326 conLoc = inSystem.getContainingLocation(); 327 forSystemCenterCheck = inSystem; 328 329 loc = Misc.interpolateVector(inSystem.getLocation(), 330 jp.getLocation(), 331 p / t); 332 varianceMult = getVarianceMult(p / t); 333 } else { // in hyper on way from jump-point to location 334 conLoc = inHyper.getContainingLocation(); 335 loc = Misc.interpolateVector(Misc.getSystemJumpPointHyperExitLocation(jp), 336 inHyper.getLocation(), 337 (p - t) / (1f - t)); 338 varianceMult = getVarianceMult((p - t) / (1f - t)); 339 } 340 loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult); 341 } 342 // from one system to a different system 343 else if (from.getContainingLocation() != to.getContainingLocation()) { 344// JumpPointAPI jp1 = Misc.findNearestJumpPointTo(from); 345// JumpPointAPI jp2 = Misc.findNearestJumpPointTo(to); 346 JumpPointAPI jp1 = findJumpPointToUse(fleet, from); 347 JumpPointAPI jp2 = findJumpPointToUse(fleet, to); 348 if (jp1 == null || jp2 == null) return; 349 float d1 = Misc.getDistance(from, jp1); 350 float d2 = Misc.getDistance(Misc.getSystemJumpPointHyperExitLocation(jp1), 351 Misc.getSystemJumpPointHyperExitLocation(jp1)); 352 float d3 = Misc.getDistance(jp2, to); 353 if (d1 < 1) d1 = 1; 354 if (d2 < 1) d2 = 1; 355 if (d3 < 1) d3 = 1; 356 357 float t1 = d1 / (d1 + d2 + d3); 358 float t2 = (d1 + d2) / (d1 + d2 + d3); 359 360 if (progress < t1) { // from "from" to jump-point 361 conLoc = from.getContainingLocation(); 362 forSystemCenterCheck = from; 363 364 loc = Misc.interpolateVector(from.getLocation(), 365 jp1.getLocation(), 366 progress / t1); 367 varianceMult = getVarianceMult(progress / t1); 368 } else if (progress < t2) { // in hyperspace, traveling between systems 369 conLoc = Global.getSector().getHyperspace(); 370 loc = Misc.interpolateVector(Misc.getSystemJumpPointHyperExitLocation(jp1), 371 Misc.getSystemJumpPointHyperExitLocation(jp2), 372 (progress - t1) / (t2 - t1)); 373 varianceMult = getVarianceMult((progress - t1) / (t2 - t1)); 374 } else { // in the "to" system, going from jp2 to to 375 conLoc = to.getContainingLocation(); 376 forSystemCenterCheck = to; 377 378 loc = Misc.interpolateVector(jp2.getLocation(), 379 to.getLocation(), 380 (progress - t2) / (1f - t2)); 381 varianceMult = getVarianceMult((progress - t2) / (1f - t2)); 382 } 383 loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult); 384 } 385 386 387 if (forSystemCenterCheck != null && forSystemCenterCheck.isSystemCenter() && 388 conLoc == forSystemCenterCheck.getContainingLocation()) { 389 loc = Misc.getPointAtRadius(forSystemCenterCheck.getLocation(), forSystemCenterCheck.getRadius() + 3000f + (float) Math.random() * 2000f); 390 } 391 392// loc = Misc.getPointWithinRadius(loc, 393// route.getMarket().getPrimaryEntity().getRadius() + 100 + (float) Math.random() * 100f); 394 395 // failsafes 396 if (conLoc == null) conLoc = from.getContainingLocation(); 397 if (loc == null) loc = new Vector2f(from.getLocation()); 398 399 400 if (fleet.getContainingLocation() != conLoc) { 401 if (fleet.getContainingLocation() != null) { 402 fleet.getContainingLocation().removeEntity(fleet); 403 } 404 conLoc.addEntity(fleet); 405 } 406 fleet.setLocation(loc.x, loc.y); 407 } 408 409 410 public static float getVarianceMult(float p) { 411 float varianceMult = 1f; 412 if (p < 0.1f) { 413 varianceMult = p / 0.1f; 414 } else if (p > 0.9f) { 415 varianceMult = (1f - p) / 0.1f; 416 } 417 return varianceMult; 418 } 419 420 421 public static JumpPointAPI findJumpPointToUse(CampaignFleetAPI fleet, SectorEntityToken from) { 422 return findJumpPointToUse(fleet.getFaction(), from); 423 } 424 public static JumpPointAPI findJumpPointToUse(FactionAPI faction, SectorEntityToken from) { 425 float min = Float.MAX_VALUE; 426 JumpPointAPI result = null; 427 float fringeMax = 0; 428 JumpPointAPI fringe = null; 429 430 LocationAPI location = from.getContainingLocation(); 431 List<JumpPointAPI> points = location.getEntities(JumpPointAPI.class); 432 433 434 for (JumpPointAPI curr : points) { 435 if (curr.getMemoryWithoutUpdate().getBoolean(JumpPointInteractionDialogPluginImpl.UNSTABLE_KEY)) { 436 continue; 437 } 438 if (curr.getMemoryWithoutUpdate().getBoolean(WormholeManager.WORMHOLE)) { 439 continue; 440 } 441 442 float dist = Misc.getDistance(from.getLocation(), curr.getLocation()); 443 if (dist < min) { 444 min = dist; 445 result = curr; 446 } 447 dist = Misc.getDistance(new Vector2f(), curr.getLocation()); 448 if (dist > fringeMax) { 449 fringe = curr; 450 fringeMax = dist; 451 } 452 } 453 454 if (from.getContainingLocation() instanceof StarSystemAPI) { 455 StarSystemAPI system = (StarSystemAPI) from.getContainingLocation(); 456 boolean useFringeOnly = !isInControlOfSystemOrEven(faction, system); 457 if (useFringeOnly && fringe != null) { 458 return fringe; 459 } 460 } 461 462 return result; 463 } 464 465 public static boolean isInControlOfSystemOrEven(FactionAPI faction, StarSystemAPI system) { 466 List<MarketAPI> markets = Misc.getMarketsInLocation(system); 467 int hostileMax = 0; 468 int ourMax = 0; 469 for (MarketAPI market : markets) { 470 if (market.getFaction().isHostileTo(faction)) { 471 hostileMax = Math.max(hostileMax, market.getSize()); 472 } else if (market.getFaction() == faction) { 473 ourMax = Math.max(ourMax, market.getSize()); 474 } 475 } 476 boolean inControl = ourMax >= hostileMax; 477 return inControl; 478 } 479} 480 481 482 483 484 485 486 487