001package com.fs.starfarer.api.impl.campaign; 002 003import java.util.Random; 004 005import org.lwjgl.util.vector.Vector2f; 006 007import com.fs.starfarer.api.campaign.CampaignFleetAPI; 008import com.fs.starfarer.api.campaign.FleetAssignment; 009import com.fs.starfarer.api.campaign.SectorEntityToken; 010import com.fs.starfarer.api.campaign.TextPanelAPI; 011import com.fs.starfarer.api.campaign.ai.FleetAssignmentDataAPI; 012import com.fs.starfarer.api.campaign.rules.MemoryAPI; 013import com.fs.starfarer.api.combat.MutableStat.StatMod; 014import com.fs.starfarer.api.impl.campaign.ghosts.BaseSensorGhost; 015import com.fs.starfarer.api.impl.campaign.ghosts.GBGoInDirection; 016import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 017import com.fs.starfarer.api.impl.campaign.ids.Tags; 018import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin; 019import com.fs.starfarer.api.ui.TooltipMakerAPI; 020import com.fs.starfarer.api.util.Misc; 021 022public class SensorArrayEntityPlugin extends BaseCampaignObjectivePlugin { 023 024 public static float SENSOR_BONUS = 700f; 025 public static float SENSOR_BONUS_MAKESHIFT = 400f; 026 //public static float SENSOR_PENALTY_MULT_FROM_HACK = 0.75f; 027 028 public void init(SectorEntityToken entity, Object pluginParams) { 029 super.init(entity, pluginParams); 030 readResolve(); 031 } 032 033 Object readResolve() { 034 return this; 035 } 036 037 public void advance(float amount) { 038 if (entity.getContainingLocation() == null || entity.isInHyperspace()) return; 039 //if (isReset()) return; 040 boolean reset = isReset(); 041 042 String id = getModId(); 043 for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) { 044 if (fleet.isInHyperspaceTransition()) continue; 045 046 if (fleet.getFaction() == entity.getFaction() || (isHacked() && fleet.getFaction().isPlayerFaction())) { 047 if (reset && !fleet.getFaction().isPlayerFaction()) { 048 respondToFalseSensorReadings(fleet); 049 } else if (reset && fleet.isPlayerFleet()) { 050 spawnPlayerSensorReading(fleet); 051 } 052 053 String desc = "Sensor array"; 054 float bonus = SENSOR_BONUS; 055 if (isMakeshift()) { 056 desc = "Makeshift sensor array"; 057 bonus = SENSOR_BONUS_MAKESHIFT; 058 } 059 060// if (fleet.getFaction() == entity.getFaction() && isHacked() && !entity.getFaction().isPlayerFaction()) { 061// fleet.getStats().addTemporaryModMult(0.1f, id, 062// desc, SENSOR_PENALTY_MULT_FROM_HACK, 063// fleet.getStats().getSensorRangeMod()); 064// } 065 066 StatMod curr = fleet.getStats().getSensorRangeMod().getFlatBonus(id); 067 if (curr == null || curr.value <= bonus) { 068 fleet.getStats().addTemporaryModFlat(0.1f, id, 069 desc, bonus, 070 fleet.getStats().getSensorRangeMod()); 071 } 072 } 073 } 074 075 } 076 077 protected boolean isMakeshift() { 078 return entity.hasTag(Tags.MAKESHIFT); 079 } 080 081 public void printEffect(TooltipMakerAPI text, float pad) { 082 int bonus = (int) SENSOR_BONUS; 083 if (isMakeshift()) { 084 bonus = (int) SENSOR_BONUS_MAKESHIFT; 085 } 086 087 text.addPara(BaseIntelPlugin.INDENT + "%s sensor range for all same-faction fleets in system", 088 pad, Misc.getHighlightColor(), "+" + bonus); 089 if (isReset()) { 090 //text.addPara(BaseIntelPlugin.INDENT + "Auto-calibrating after factory reset; non-functional", 3f); 091 text.addPara(BaseIntelPlugin.INDENT + "Generating false readings", 3f); 092 } 093 094// text.addPara(BaseIntelPlugin.INDENT + "%s sensor range to same-faction fleets when hacked", 095// 0f, Misc.getHighlightColor(), "-" + (int) Math.round((1f - SENSOR_PENALTY_MULT_FROM_HACK) * 100f) + "%"); 096 } 097 098 public void printNonFunctionalAndHackDescription(TextPanelAPI text) { 099 if (entity.getMemoryWithoutUpdate().getBoolean(MemFlags.OBJECTIVE_NON_FUNCTIONAL)) { 100 text.addPara("This one, however, does not appear to be transmitting a sensor telemetry broadcast. The cause of its lack of function is unknown."); 101 } 102 if (isHacked()) { 103 text.addPara("You have a hack running on this sensor array."); 104 } 105// if (isReset()) { 106// text.addPara("This sensor array is auto-calibrating after a factory reset and is effectively non-functional."); 107// } 108 } 109 110 111 112 @Override 113 public void addHackStatusToTooltip(TooltipMakerAPI text, float pad) { 114 int bonus = (int) SENSOR_BONUS; 115 if (isMakeshift()) { 116 bonus = (int) SENSOR_BONUS_MAKESHIFT; 117 } 118 text.addPara("%s sensor range for in-system fleets", 119 pad, Misc.getHighlightColor(), "+" + bonus); 120 121// text.addPara("%s%% sensor range when hacked", 122// pad, Misc.getHighlightColor(), "-" + (int) Math.round((1f - SENSOR_PENALTY_MULT_FROM_HACK) * 100f)); 123 124 super.addHackStatusToTooltip(text, pad); 125 } 126 127 protected String getModId() { 128 return "sensor_array"; 129 } 130 131 132 public static String GHOST_RESPONSE = "ghost_response"; // custom value added to assignments so we know which to clear 133 134 protected void spawnPlayerSensorReading(CampaignFleetAPI fleet) { 135 Random random = Misc.random; 136 137 MemoryAPI mem = fleet.getMemoryWithoutUpdate(); 138 if (mem.getBoolean(MemFlags.FLEET_NOT_CHASING_GHOST)) { 139 return; 140 } 141 if (mem.getBoolean(MemFlags.FLEET_CHASING_GHOST)) { 142 return; 143 } 144 boolean spawnReading = random.nextFloat() < 0.5f; 145 //spawnReading = true; 146 if (!spawnReading) { 147 mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, 1f + 2f * random.nextFloat()); 148 return; 149 } 150 151 float dur = 3f + 3f + random.nextFloat(); 152 mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, dur * 0.5f); 153 154 BaseSensorGhost g = new BaseSensorGhost(null, 0); 155 156 float r = random.nextFloat(); 157 int maxBurn; 158 if (r < 0.25f) { 159 g.initEntity(g.genMediumSensorProfile(), g.genSmallRadius(), 0, fleet.getContainingLocation()); 160 maxBurn = 9 + random.nextInt(3); 161 } else if (r < 0.6f) { 162 g.initEntity(g.genLargeSensorProfile(), g.genMediumRadius(), 0, fleet.getContainingLocation()); 163 maxBurn = 8 + random.nextInt(3); 164 } else { 165 g.initEntity(g.genLargeSensorProfile(), g.genLargeRadius(), 0, fleet.getContainingLocation()); 166 maxBurn = 7 + random.nextInt(3); 167 } 168 169 170 if (!g.placeNearPlayer()) { 171 return; 172 } 173 //g.setDespawnRange(200f); 174 175 176 float speed = Misc.getSpeedForBurnLevel(maxBurn); 177 float accelMult = speed / Misc.getSpeedForBurnLevel(20f); 178 if (accelMult < 0.1f) accelMult = 0.1f; 179 g.setAccelMult(1f/ accelMult); 180 181 float dir = Misc.getAngleInDegrees(g.getEntity().getLocation(), fleet.getLocation()); 182 float sign = Math.signum(random.nextFloat() - 0.5f); 183 dir += sign * (30f + random.nextFloat() * 60f); 184 185 186 g.addBehavior(new GBGoInDirection(dur, dir, maxBurn)); 187 188 fleet.getContainingLocation().addScript(g); 189 190 } 191 192 protected void respondToFalseSensorReadings(CampaignFleetAPI fleet) { 193 if (fleet.isStationMode()) return; 194 if (fleet.getAI() == null) { 195 return; 196 } 197 if (fleet.getAI().getAssignmentsCopy() == null) { 198 return; 199 } 200 201 MemoryAPI mem = fleet.getMemoryWithoutUpdate(); 202 if (mem.getBoolean(MemFlags.FLEET_NOT_CHASING_GHOST)) { 203 return; 204 } 205 if (mem.getBoolean(MemFlags.FLEET_CHASING_GHOST)) { 206 return; 207 } 208 if (mem.getBoolean(MemFlags.FLEET_BUSY)) { 209 return; 210 } 211 boolean patrol = mem.getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET); 212 boolean warFleet = mem.getBoolean(MemFlags.MEMORY_KEY_WAR_FLEET); 213 boolean pirate = mem.getBoolean(MemFlags.MEMORY_KEY_PIRATE); 214 if (!patrol && !warFleet && !pirate) { 215 return; 216 } 217 218 Random random = (Random) mem.get(MemFlags.FLEET_CHASING_GHOST_RANDOM); 219 if (random == null) { 220 random = Misc.getRandom(Misc.getSalvageSeed(fleet), 7); 221 mem.set(MemFlags.FLEET_CHASING_GHOST_RANDOM, random, 30f); 222 } 223 224 boolean willRespond = random.nextFloat() < 0.75f; 225 //willRespond = true; 226 if (!willRespond) { 227 mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, 1f + 1f * random.nextFloat()); 228 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 229 MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, false, 0f); 230 for (FleetAssignmentDataAPI curr : fleet.getAI().getAssignmentsCopy()) { 231 if (GHOST_RESPONSE.equals(curr.getCustom())) { 232 fleet.getAI().removeAssignment(curr); 233 } 234 } 235 return; 236 } 237 238 float chaseDur = (2.5f + (float) Math.random()) * 2f; 239 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 240 MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, true, chaseDur); 241 mem.set(MemFlags.FLEET_BUSY, true, chaseDur); 242 mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, chaseDur + 8f + 4f * random.nextFloat()); 243 244 float angle = Misc.getAngleInDegrees(fleet.getLocation()); // away from center of system; 245 float arc = 270f; 246 angle += arc/2f - arc * random.nextFloat(); 247 float dist = 3000f + 3000f * random.nextFloat(); 248 Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle); 249 loc.scale(dist); 250 Vector2f.add(loc, fleet.getLocation(), loc); 251 252 253 String actionText = "investigating anomalous sensor reading"; 254 255 SectorEntityToken target = fleet.getContainingLocation().createToken(loc); 256 fleet.addAssignmentAtStart(FleetAssignment.PATROL_SYSTEM, target, 3f, actionText, null); 257 FleetAssignmentDataAPI curr = fleet.getCurrentAssignment(); 258 if (curr != null) { 259 curr.setCustom(GHOST_RESPONSE); 260 } 261 262 if (dist > 2000f) { 263 fleet.addAssignmentAtStart(FleetAssignment.GO_TO_LOCATION, target, 3f, actionText, null); 264 curr = fleet.getCurrentAssignment(); 265 if (curr != null) { 266 curr.setCustom(GHOST_RESPONSE); 267 } 268 } 269 } 270 271// protected void unrespond(CampaignFleetAPI fleet) { 272// Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 273// MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, false, 0f); 274// for (FleetAssignmentDataAPI curr : fleet.getAI().getAssignmentsCopy()) { 275// if (GHOST_RESPONSE.equals(curr.getCustom())) { 276// fleet.getAI().removeAssignment(curr); 277// } 278// } 279// } 280// 281// protected void respond(CampaignFleetAPI fleet) { 282// unrespond(fleet); 283// 284// Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 285// MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, true, (1.5f + (float) Math.random()) * 0.2f); 286// 287// fleet.addAssignmentAtStart(FleetAssignment.PATROL_SYSTEM, params.target, 3f, params.actionText, null); 288// FleetAssignmentDataAPI curr = fleet.getCurrentAssignment(); 289// if (curr != null) { 290// curr.setCustom(GHOST_RESPONSE); 291// } 292// 293// float dist = Misc.getDistance(params.target, fleet); 294// if (dist > 2000f) { 295// fleet.addAssignmentAtStart(FleetAssignment.GO_TO_LOCATION, params.target, 3f, params.travelText, null); 296// //fleet.addAssignmentAtStart(FleetAssignment.DELIVER_CREW, params.target, 3f, params.travelText, null); 297// curr = fleet.getCurrentAssignment(); 298// if (curr != null) { 299// curr.setCustom(GHOST_RESPONSE); 300// } 301// } 302// 303// //Global.getSector().addPing(fleet, Pings.DANGER); 304// } 305} 306 307 308