001package com.fs.starfarer.api.impl.campaign.intel.group; 002 003import java.util.ArrayList; 004import java.util.LinkedHashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.CampaignFleetAPI; 012import com.fs.starfarer.api.campaign.FleetAssignment; 013import com.fs.starfarer.api.campaign.JumpPointAPI; 014import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination; 015import com.fs.starfarer.api.campaign.LocationAPI; 016import com.fs.starfarer.api.campaign.SectorEntityToken; 017import com.fs.starfarer.api.campaign.StarSystemAPI; 018import com.fs.starfarer.api.impl.campaign.fleets.RouteLocationCalculator; 019import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData; 020import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment; 021import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 022import com.fs.starfarer.api.util.IntervalUtil; 023import com.fs.starfarer.api.util.Misc; 024 025public class FGTravelAction extends BaseFGAction { 026 027 protected SectorEntityToken from, to; 028 protected IntervalUtil interval = new IntervalUtil(0.1f, 0.3f); 029 030 protected String travelText; 031 protected String waitingAtDestinationText; 032 protected String rendezvousTravelText; 033 protected String waitingRendezvousText; 034 protected boolean doNotGetSidetracked = true; 035 036 public FGTravelAction(SectorEntityToken from, SectorEntityToken to) { 037 this.from = from; 038 this.to = to; 039 040 travelText = "traveling to " + to.getName(); 041 waitingAtDestinationText = "orbiting " + to.getName(); 042 waitingRendezvousText = "waiting at rendezvous point"; 043 rendezvousTravelText = "traveling to rendezvous point"; 044 045 if (to.getContainingLocation() instanceof StarSystemAPI) { 046 StarSystemAPI system = (StarSystemAPI) to.getContainingLocation(); 047 if (to == system.getCenter()) { 048 travelText = "traveling to the " + system.getNameWithLowercaseTypeShort(); 049 } 050 } 051 052 interval.forceIntervalElapsed(); 053 } 054 055 @Override 056 public void addRouteSegment(RouteData route) { 057 float travelDays = RouteLocationCalculator.getTravelDays(from, to); 058 travelDays *= 1.5f; 059 RouteSegment segment = new RouteSegment(null, travelDays, from, to, null); 060 route.addSegment(segment); 061 } 062 063 public static float computeETADays(CampaignFleetAPI fleet, SectorEntityToken dest) { 064 boolean sameLoc = fleet.getContainingLocation() == dest.getContainingLocation(); 065 float dist = 0f; 066 boolean destIsSystem = dest.getStarSystem() != null && dest.getStarSystem().getCenter() == dest; 067 if (sameLoc) { 068 dist = Misc.getDistance(fleet, dest); 069 if (destIsSystem) dist = 0f; 070 } else { 071 dist = Misc.getDistance(fleet.getLocationInHyperspace(), dest.getLocationInHyperspace()); 072 073 if (!destIsSystem) { 074 dist += 5000f; // fudge factor for traveling in-system 075 } 076 077 if (fleet.getContainingLocation() != null && 078 dest.getContainingLocation() != null && 079 !fleet.getContainingLocation().isHyperspace() && 080 !dest.getContainingLocation().isHyperspace()) { 081 dist += 5000f; // two legs of travel are in-system 082 } 083 } 084 085 return dist / 1000f; 086 } 087 088 public float getEstimatedDaysToComplete() { 089 if (intel.isSpawnedFleets()) { 090 float totalETA = 0f; 091 float totalStr = 0f; 092 for (CampaignFleetAPI fleet : intel.getFleets()) { 093 float eta = computeETADays(fleet, to); 094 float w = fleet.getEffectiveStrength(); 095 totalETA += eta * w; 096 totalStr += w; 097 } 098 099 float eta = totalETA / Math.max(1f, totalStr); 100 return eta; 101 } else { 102 RouteSegment segment = intel.getSegmentForAction(this); 103 if (segment == null) return 0f; 104 return Math.max(0f, segment.daysMax - segment.elapsed); 105 } 106 } 107 108 109 @Override 110 public void notifySegmentFinished(RouteSegment segment) { 111 super.notifySegmentFinished(segment); 112 113 if (FleetGroupIntel.DEBUG) { 114 System.out.println("FGTravelAction.notifySegmentFinished() " + segment.getTransitProgress() + 115 " [" + from.getName() + " -> " + to.getName() + "]"); 116 } 117 } 118 119 @Override 120 public void notifyFleetsSpawnedMidSegment(RouteSegment segment) { 121 super.notifyFleetsSpawnedMidSegment(segment); 122 123 if (FleetGroupIntel.DEBUG) { 124 System.out.println("FGTravelAction.notifyFleetsSpawnedMidSegment() " + segment.getTransitProgress() + 125 " [" + from.getName() + " -> " + to.getName() + "]"); 126 } 127 } 128 129 @Override 130 public void directFleets(float amount) { 131 List<CampaignFleetAPI> fleets = intel.getFleets(); 132 if (fleets.isEmpty()) { 133 setActionFinished(true); 134 return; 135 } 136 137 float days = Global.getSector().getClock().convertToDays(amount); 138 interval.advance(days); 139 140 if (!interval.intervalElapsed()) return; 141 142 143 144 //JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleet, current.from); 145 146 Set<LocationAPI> locations = new LinkedHashSet<LocationAPI>(); 147 Set<LocationAPI> locationsWithBattles = new LinkedHashSet<LocationAPI>(); 148 int inFrom = 0; 149 int inTo = 0; 150 int inHyper = 0; 151 for (CampaignFleetAPI fleet : fleets) { 152 LocationAPI conLoc = fleet.getContainingLocation(); 153 locations.add(conLoc); 154 if (fleet.getBattle() != null) { 155 locationsWithBattles.add(conLoc); 156 } 157 158 if (to.getContainingLocation() == conLoc) { 159 inTo++; 160 } else if (from.getContainingLocation() == conLoc && !conLoc.isHyperspace()) { 161 inFrom++; 162 } else { 163 inHyper++; 164 } 165 166 //fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_IGNORES_OTHER_FLEETS, true, 0.4f); 167 } 168 169 for (CampaignFleetAPI fleet : fleets) { 170 if (doNotGetSidetracked && !locationsWithBattles.contains(fleet.getContainingLocation())) { 171 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_DO_NOT_GET_SIDETRACKED, true, 0.4f); 172 } 173 } 174 175 boolean allInSameLocation = locations.size() == 1; 176 177 if (allInSameLocation && inTo > 0 && to.getContainingLocation() instanceof StarSystemAPI) { 178 // was the travel order just to the system, rather than a specific entity in it? 179 StarSystemAPI system = (StarSystemAPI) to.getContainingLocation(); 180 if (to == system.getCenter()) { 181 setActionFinished(true); 182 return; 183 } 184 } 185 186 187 if (allInSameLocation) { 188 boolean allNear = true; 189 190 Vector2f com = new Vector2f(); 191 float weight = 0f; 192 String key = "$FGTravelAction_ignoreFleetForCenterOfMass"; 193 for (CampaignFleetAPI fleet : fleets) { 194 boolean near = fleet.getContainingLocation() == to.getContainingLocation() && 195 Misc.getDistance(fleet, to) < to.getRadius() + 500f; 196 allNear &= near; 197 198 if (Misc.isSlowMoving(fleet)) { 199 fleet.getMemoryWithoutUpdate().set(key, true, 2f); 200 } 201 if (fleet.getMemoryWithoutUpdate().getBoolean(key)) { 202 continue; 203 } 204 205 float w = fleet.getFleetPoints(); 206 Vector2f loc = new Vector2f(fleet.getLocation()); 207 loc.scale(w); 208 Vector2f.add(com, loc, com); 209 weight += w; 210 } 211 212 if (weight < 1f) { 213 weight = 1f; 214 if (!fleets.isEmpty()) { 215 com.set(fleets.get(0).getLocation()); 216 } 217 } 218 com.scale(1f / weight); 219 220 Vector2f dest = null; 221 if (inFrom > 0) { 222 JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), from); 223 dest = jp.getLocation(); 224 } else if (inHyper > 0) { 225 JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), to); 226 SectorEntityToken jumpExit = null; 227 for (JumpDestination jd : jp.getDestinations()) { 228 if (jd.getDestination() != null && jd.getDestination().isInHyperspace()) { 229 jumpExit = jd.getDestination(); 230 break; 231 } 232 } 233 if (jumpExit != null) { 234 dest = jumpExit.getLocation(); 235 } else { 236 dest = to.getLocationInHyperspace(); 237 } 238 } else { 239 dest = to.getLocation(); 240 } 241 242 if (dest == null) { 243 setActionFinished(true); 244 return; 245 } 246 247 float angle = Misc.getAngleInDegrees(com, dest); 248 float distComToDest = Misc.getDistance(com, dest); 249 float offset = Math.min(distComToDest, 5000f); 250 251 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle); 252 dir.scale(offset); 253 dest = new Vector2f(dest); 254 Vector2f.add(com, dir, dest); 255 256 SectorEntityToken movementToken = fleets.get(0).getContainingLocation().createToken(dest); 257 //SectorEntityToken comToken = fleets.get(0).getContainingLocation().createToken(com); 258 259 float comLeashRange = 750f; 260 int numFleets = fleets.size(); 261 comLeashRange += Math.min(Math.max(numFleets - 5, 0) * 100f, 500f); 262 263 for (CampaignFleetAPI fleet : fleets) { 264 fleet.clearAssignments(); 265// if (fleet.getName().contains("Operations")) { 266// System.out.println("efwefwe"); 267// } 268 269// if (from.isInCurrentLocation() && from.getName().contains("Tactistar")) { 270// System.out.println("efwfe"); 271// } 272 273 //fleet.getMemoryWithoutUpdate().set("$preferJumpPointAt", dest, 2f); 274 275 float toCom = Misc.getDistance(fleet.getLocation(), com); 276 float toDest = Misc.getDistance(fleet.getLocation(), dest); 277 278 if (inTo > 0 && distComToDest < 500f + to.getRadius() && toCom < 500f) { 279 fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, to, 3f, waitingAtDestinationText); 280 } else if (inTo <= 0 && toCom < 1000 && (toDest < 750 || distComToDest < 500f)) { 281 // close to the jump-point; take it by having the move order use to rather than movementToken as the target 282 fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, to, 3f, travelText); 283 //fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_IGNORES_OTHER_FLEETS, true, 0.4f); 284 } else if (toCom > comLeashRange) { 285 angle = Misc.getAngleInDegrees(fleet.getLocation(), com); 286 dir = Misc.getUnitVectorAtDegreeAngle(angle); 287 // need to overshoot to make sure Sustained Burn is used 288 dir.scale(5000f); 289 Vector2f overshootCom = Vector2f.add(com, dir, new Vector2f()); 290 SectorEntityToken comToken = fleets.get(0).getContainingLocation().createToken(overshootCom); 291 fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, comToken, 3f, travelText); 292 } else { 293 fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, movementToken, 3f, travelText); 294 } 295 } 296 297 if (allNear) { 298 setActionFinished(true); 299 return; 300 } 301 302 } else { 303 SectorEntityToken rendezvous = null; 304 if (inTo <= 0) { 305 JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), from); 306// JumpPointAPI jp = null; 307// for (CampaignFleetAPI fleet : fleets) { 308// if (fleet.getContainingLocation() == from.getContainingLocation()) { 309// jp = Misc.findNearestJumpPointTo(fleet); 310// break; 311// } 312// } 313 SectorEntityToken jumpExit = null; 314 for (JumpDestination jd : jp.getDestinations()) { 315 if (jd.getDestination() != null && jd.getDestination().isInHyperspace()) { 316 jumpExit = jd.getDestination(); 317 break; 318 } 319 } 320 if (jumpExit != null) { 321 rendezvous = jumpExit; 322 } 323 } else { 324 //rendezvous = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), to); 325 List<SectorEntityToken> potential = new ArrayList<SectorEntityToken>(); 326 for (CampaignFleetAPI fleet : fleets) { 327 if (fleet.getContainingLocation() == to.getContainingLocation()) { 328 SectorEntityToken test = Misc.findNearestJumpPointTo(fleet); 329 if (test != null) { 330 potential.add(test); 331 } 332// rendezvous = Misc.findNearestJumpPointTo(fleet); 333// break; 334 } 335 } 336 float bestScore = Float.MAX_VALUE; // want a low score 337 for (SectorEntityToken curr : potential) { 338 float score = 0f; 339 for (CampaignFleetAPI fleet : fleets) { 340 if (fleet.getContainingLocation() == to.getContainingLocation()) { 341 float dist = Misc.getDistance(curr, fleet); 342 score += dist * fleet.getFleetPoints(); 343 } 344 } 345 if (score < bestScore) { 346 bestScore = score; 347 rendezvous = curr; 348 } 349 } 350 } 351 352 if (rendezvous == null) { 353 // something is majorly wrong, i.e. a system not connected to hyperspace 354 setActionFinished(true); 355 return; 356 } 357 358 for (CampaignFleetAPI fleet : fleets) { 359 fleet.clearAssignments(); 360 361 if (fleet.getContainingLocation() != rendezvous.getContainingLocation()) { 362 // catching up to other fleets 363 fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, to, 3f, travelText); 364 } else { 365 float dist = Misc.getDistance(rendezvous, fleet); 366 if (dist < 500f) { 367 fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, rendezvous, 3f, waitingRendezvousText); 368 } else { 369 fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, rendezvous, 3f, rendezvousTravelText); 370 } 371 } 372 } 373 } 374 } 375 376 public String getTravelText() { 377 return travelText; 378 } 379 380 public void setTravelText(String travelText) { 381 this.travelText = travelText; 382 } 383 384 public String getWaitingAtDestinationText() { 385 return waitingAtDestinationText; 386 } 387 388 public void setWaitingAtDestinationText(String waitingAtDestinationText) { 389 this.waitingAtDestinationText = waitingAtDestinationText; 390 } 391 392 public String getRendezvousTravelText() { 393 return rendezvousTravelText; 394 } 395 396 public void setRendezvousTravelText(String rendezvousTravelText) { 397 this.rendezvousTravelText = rendezvousTravelText; 398 } 399 400 public String getWaitingRendezvousText() { 401 return waitingRendezvousText; 402 } 403 404 public void setWaitingRendezvousText(String waitingRendezvousText) { 405 this.waitingRendezvousText = waitingRendezvousText; 406 } 407 408 public SectorEntityToken getFrom() { 409 return from; 410 } 411 412 public SectorEntityToken getTo() { 413 return to; 414 } 415 416 public void setFrom(SectorEntityToken from) { 417 this.from = from; 418 } 419 420 public void setTo(SectorEntityToken to) { 421 this.to = to; 422 } 423 424 public boolean isDoNotGetSidetracked() { 425 return doNotGetSidetracked; 426 } 427 428 public void setDoNotGetSidetracked(boolean doNotGetSidetracked) { 429 this.doNotGetSidetracked = doNotGetSidetracked; 430 } 431 432} 433 434 435 436