001package com.fs.starfarer.api.impl.campaign.procgen; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Iterator; 006import java.util.List; 007import java.util.Random; 008 009import org.json.JSONArray; 010import org.json.JSONException; 011import org.json.JSONObject; 012 013import com.fs.starfarer.api.Global; 014import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; 015import com.fs.starfarer.api.campaign.CargoStackAPI; 016import com.fs.starfarer.api.campaign.SpecialItemData; 017import com.fs.starfarer.api.campaign.SpecialItemPlugin; 018import com.fs.starfarer.api.campaign.SpecialItemSpecAPI; 019import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI; 020import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize; 021import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 022import com.fs.starfarer.api.impl.campaign.ids.Tags; 023import com.fs.starfarer.api.loading.FighterWingSpecAPI; 024import com.fs.starfarer.api.loading.WeaponSpecAPI; 025import com.fs.starfarer.api.util.Misc; 026import com.fs.starfarer.api.util.WeightedRandomPicker; 027 028public class DropGroupRow implements Cloneable { 029 public static final String NOTHING = "nothing"; 030 031 032 public static final String WEAPON_PREFIX = "wpn_"; 033 //public static final String MOD_PREFIX = "mod_"; 034 public static final String FIGHTER_PREFIX = "ftr_"; 035 public static final String ITEM_PREFIX = "item_"; 036 037 038 private String commodity, group; 039 private float freq; 040 private boolean percent = false; 041 042 private boolean multiValued = false; 043 private int tier = -1; 044 private List<String> tags = new ArrayList<String>(); 045 //private List<String> tags2 = new ArrayList<String>(); 046 private WeaponType weaponType = null; 047 private WeaponSize weaponSize = null; 048 049// private String itemId = null; 050// private String itemParams = null; 051 052 Object writeReplace() { 053 DropGroupRow copy = clone(); 054 if (tags != null && tags.isEmpty()) { 055 copy.tags = null; 056 } 057// if (tags2 != null && tags2.isEmpty()) { 058// copy.tags2 = null; 059// } 060 return copy; 061 //return this; 062 } 063 064 @Override 065 public DropGroupRow clone() { 066 try { 067 DropGroupRow copy = (DropGroupRow) super.clone(); 068 return copy; 069 } catch (CloneNotSupportedException e) { 070 return null; 071 } 072 } 073 074 public DropGroupRow(JSONObject row) throws JSONException { 075 commodity = row.getString("commodity"); 076 group = row.getString("group"); 077 078 String fStr = row.getString("freq"); 079 if (fStr.endsWith("%")) { 080 percent = true; 081 fStr = fStr.substring(0, fStr.length() - 1); 082 freq = Float.parseFloat(fStr); 083 } else { 084 freq = (float) row.getDouble("freq"); 085 } 086 parseData(); 087 } 088 089 private void parseData() throws JSONException { 090 if (commodity.contains(":")) { 091 multiValued = true; 092 093 if (commodity.startsWith(ITEM_PREFIX)) { 094// item_factory_core resolved 095// item_modspec:converted_hangar resolved 096// item_modspec:{} unresolved 097// item_:{} unresolved 098// item_modspec:{tier:3, tags:[shields]} unresolved 099// item_:{tags:[modspec], p:{tier:3, tags:[engines]} unresolved 100 101 String test = commodity.replaceFirst(ITEM_PREFIX, ""); 102 int index = test.indexOf(':'); 103 if (index < 0) { 104 multiValued = false; 105 } else { 106 String itemId = test.substring(0, index); 107 String secondPart = test.substring(index + 1); 108 109 if (itemId.isEmpty() && secondPart.startsWith("{")) { 110 JSONObject json = new JSONObject(secondPart); 111 112 tier = json.optInt("tier", -1); 113 if (json.has("tags")) { 114 JSONArray tags = json.getJSONArray("tags"); 115 for (int i = 0; i < tags.length(); i++) { 116 this.tags.add(tags.getString(i)); 117 } 118 } 119 } else if (!secondPart.startsWith("{")) { 120 multiValued = false; 121 } 122 } 123 return; 124 } 125 126 127 JSONObject json = new JSONObject(commodity.substring(commodity.indexOf(":") + 1)); 128 tier = json.optInt("tier", -1); 129 if (json.has("tags")) { 130 JSONArray tags = json.getJSONArray("tags"); 131 for (int i = 0; i < tags.length(); i++) { 132 this.tags.add(tags.getString(i)); 133 } 134 } 135 if (json.has("weaponType")) { 136 weaponType = Misc.mapToEnum(json, "weaponType", WeaponType.class, null); 137 } 138 if (json.has("weaponSize")) { 139 weaponSize = Misc.mapToEnum(json, "weaponSize", WeaponSize.class, null); 140 } 141 } 142 } 143 144 public DropGroupRow(String commodity, String group, float freq) { 145 this.commodity = commodity; 146 this.group = group; 147 this.freq = freq; 148 try { 149 parseData(); 150 } catch (JSONException e) { 151 throw new RuntimeException(e); 152 } 153 } 154 155 public CommoditySpecAPI getSpec() { 156 if (isNothing() || isWeapon()) return null; 157 158 CommoditySpecAPI spec = Global.getSector().getEconomy().getCommoditySpec(commodity); 159 return spec; 160 } 161 162 public WeaponSpecAPI getWeaponSpec() { 163 if (!isWeapon()) return null; 164 165 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(getWeaponId()); 166 return spec; 167 } 168 169// public HullModSpecAPI getHullModSpec() { 170// if (!isHullMod()) return null; 171// 172// HullModSpecAPI spec = Global.getSettings().getHullModSpec(getHullModId()); 173// return spec; 174// } 175 176 public float getBaseUnitValue() { 177 if (isMultiValued()) throw new RuntimeException("Call resolveToSpecificItem() before calling getBaseUnitValue()"); 178 179 if (isWeapon()) { 180 return getWeaponSpec().getBaseValue(); 181// } else if (isHullMod()) { 182// return getHullModSpec().getBaseValue(); 183 } else if (isFighterWing()) { 184 return getFighterWingSpec().getBaseValue(); 185 } else if (isSpecialItem()) { 186 CargoStackAPI stack = Global.getFactory().createCargoStack(CargoItemType.SPECIAL, 187 new SpecialItemData(getSpecialItemId(), getSpecialItemData()), null); 188 return stack.getPlugin().getPrice(null, null); 189 } else { 190 return getSpec().getBasePrice(); 191 } 192 } 193 194 public FighterWingSpecAPI getFighterWingSpec() { 195 if (!isFighterWing()) return null; 196 197 FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(getFighterWingId()); 198 return spec; 199 } 200 201 public boolean isCommodity() { 202 return !isNothing() && !isWeapon() && !isFighterWing() && !isSpecialItem(); 203 } 204 205 public boolean isWeapon() { 206 return commodity != null && commodity.startsWith(WEAPON_PREFIX); 207 } 208 209 public String getWeaponId() { 210 return commodity.substring(WEAPON_PREFIX.length()); 211 } 212 213 public String getSpecialItemId() { 214 String afterPrefix = commodity.substring(ITEM_PREFIX.length()); 215 int index = afterPrefix.indexOf(":"); 216 if (index >= 0) { 217 afterPrefix = afterPrefix.substring(0, index); 218 } 219 return afterPrefix; 220 } 221 public String getSpecialItemData() { 222 String afterPrefix = commodity.substring(ITEM_PREFIX.length()); 223 int index = afterPrefix.indexOf(":"); 224 if (index >= 0) { 225 afterPrefix = afterPrefix.substring(index + 1); 226 return afterPrefix; 227 } 228 return null; 229 } 230 231 public SpecialItemSpecAPI getSpecialItemSpec() { 232 if (!isSpecialItem()) return null; 233 234 SpecialItemSpecAPI spec = Global.getSettings().getSpecialItemSpec(getSpecialItemId()); 235 return spec; 236 } 237 238 public boolean isFighterWing() { 239 return commodity != null && commodity.startsWith(FIGHTER_PREFIX); 240 } 241 public boolean isSpecialItem() { 242 return commodity != null && commodity.startsWith(ITEM_PREFIX); 243 } 244 245 public String getFighterWingId() { 246 return commodity.substring(FIGHTER_PREFIX.length()); 247 } 248 249// public boolean isHullMod() { 250// return commodity != null && commodity.startsWith(MOD_PREFIX); 251// } 252// 253// public String getHullModId() { 254// return commodity.substring(MOD_PREFIX.length()); 255// } 256 257 public boolean isMultiValued() { 258 return multiValued; 259 } 260 261 public boolean isNothing() { 262 return commodity == null || commodity.equals(NOTHING); 263 } 264 265 public String getCommodity() { 266 return commodity; 267 } 268 269 public void setCommodity(String commodity) { 270 this.commodity = commodity; 271 } 272 273 public String getGroup() { 274 return group; 275 } 276 277 public void setGroup(String group) { 278 this.group = group; 279 } 280 281 public float getFreq() { 282 return freq; 283 } 284 285 public void setFreq(float freq) { 286 this.freq = freq; 287 } 288 289 290 public static WeightedRandomPicker<DropGroupRow> getPicker(String group) { 291 WeightedRandomPicker<DropGroupRow> picker = new WeightedRandomPicker<DropGroupRow>(); 292 Collection<DropGroupRow> specs = Global.getSettings().getAllSpecs(DropGroupRow.class); 293 294 for (DropGroupRow spec : specs) { 295// if (!spec.isMultiValued() && spec.isHullMod() && spec.getHullModSpec().hasTag(Tags.HULLMOD_NO_DROP)) { 296// continue; 297// } 298 if (!spec.isMultiValued() && spec.isFighterWing() && spec.getFighterWingSpec().hasTag(Tags.WING_NO_DROP)) { 299 continue; 300 } 301 302 if (spec.getGroup().equals(group)) { 303 picker.add(spec, spec.getFreq()); 304 } 305 } 306 307 for (DropGroupRow curr : picker.getItems()) { 308 if (curr.isNothing() && curr.percent) { 309 float totalOther = picker.getTotal() - curr.freq; 310 311 float fNothing = curr.freq / 100f; 312 if (fNothing < 0) fNothing = 0; 313 if (fNothing > 1) fNothing = 1; 314 315 float weightNothing = totalOther * fNothing / (1f - fNothing); 316 317 picker.setWeight(picker.getItems().indexOf(curr), weightNothing); 318 break; 319 } 320 } 321 322 if (picker.isEmpty()) { 323 throw new RuntimeException("No drop data found for drop group [" + group + "], probably an error in drop_groups.csv"); 324 } 325 326 return picker; 327 } 328 329 330 public DropGroupRow resolveToSpecificItem(Random random) { 331 if (random == null) random = new Random(); 332 333 if (!isMultiValued()) return this; 334 335 DropGroupRow copy = clone(); 336 copy.multiValued = false; 337 338 if (isSpecialItem()) { 339// item_factory_core resolved 340// item_modspec:converted_hangar resolved 341// item_modspec:{} unresolved 342// item_:{} unresolved 343// item_modspec:{tier:3, tags:[shields]} unresolved 344// item_:{tags:[modspec], p:{tier:3, tags:[engines]} unresolved 345 346 String test = commodity.replaceFirst(ITEM_PREFIX, ""); 347 int index = test.indexOf(':'); 348 if (index < 0) { 349 } else { 350 boolean getParamsFromP = false; 351 String itemId = test.substring(0, index); 352 String params = test.substring(index + 1); 353 354 if (!itemId.isEmpty()) { 355 // params are already set properly, and we have an item id - do nothing 356 } else { 357 List<SpecialItemSpecAPI> specs = Global.getSettings().getAllSpecialItemSpecs(); 358 359 Iterator<SpecialItemSpecAPI> iter = specs.iterator(); 360// while (iter.hasNext()) { 361// SpecialItemSpecAPI curr = iter.next(); 362// if (curr.isHidden() || curr.isHiddenEverywhere()) iter.remove(); 363// } 364 365 if (!tags.isEmpty()) { 366 iter = specs.iterator(); 367 while (iter.hasNext()) { 368 SpecialItemSpecAPI curr = iter.next(); 369 for (String tag : tags) { 370 boolean not = tag.startsWith("!"); 371 tag = not ? tag.substring(1) : tag; 372 boolean has = curr.hasTag(tag); 373 if (not == has) { 374 iter.remove(); 375 break; 376 } 377 } 378 } 379 } 380 381 WeightedRandomPicker<SpecialItemSpecAPI> picker = new WeightedRandomPicker<SpecialItemSpecAPI>(random); 382 for (SpecialItemSpecAPI spec : specs) { 383 picker.add(spec, 1f * spec.getRarity()); 384 } 385 SpecialItemSpecAPI pick = picker.pick(); 386 if (pick == null) { 387 copy.commodity = NOTHING; 388 } else { 389 itemId = pick.getId(); 390 getParamsFromP = true; 391 } 392 } 393 394 // we've picked an itemId to use 395 if (!itemId.isEmpty()) { 396// item_:{tags:[modspec], p:{tier:3, tags:[engines]} unresolved 397 try { 398 if (getParamsFromP) { 399 JSONObject json = new JSONObject(params); 400 if (json.has("p")) { 401 params = json.getJSONObject("p").toString(); 402 } else { 403 params = "{}"; 404 } 405 } 406 407 SpecialItemSpecAPI spec = Global.getSettings().getSpecialItemSpec(itemId); 408 SpecialItemPlugin plugin = spec.getNewPluginInstance(null); 409 String itemData = plugin.resolveDropParamsToSpecificItemData(params, random); 410 411 if (itemData == null) { 412 copy.commodity = NOTHING; 413 } else if (itemData.isEmpty()) { 414 copy.commodity = ITEM_PREFIX + itemId; 415 } else { 416 copy.commodity = ITEM_PREFIX + itemId + ":" + itemData; 417 } 418// if (copy.commodity.contains("{")) { 419// System.out.println("wefwefew"); 420// } 421 } catch (JSONException e) { 422 throw new RuntimeException("Params: " + params, e); 423 } 424 } else { 425 copy.commodity = NOTHING; 426 } 427 } 428// } else if (isHullMod()) { 429// List<HullModSpecAPI> specs = Global.getSettings().getAllHullModSpecs(); 430// 431// Iterator<HullModSpecAPI> iter = specs.iterator(); 432// while (iter.hasNext()) { 433// HullModSpecAPI curr = iter.next(); 434// if (curr.isHidden() || curr.isHiddenEverywhere()) iter.remove(); 435// } 436// 437// if (tier >= 0) { 438// iter = specs.iterator(); 439// while (iter.hasNext()) { 440// HullModSpecAPI curr = iter.next(); 441//// if (curr.getId().contains("armor")) { 442//// System.out.println("wfwefwe"); 443//// } 444// if (curr.getTier() != tier) iter.remove(); 445// } 446// } 447// 448// if (!tags.isEmpty()) { 449// iter = specs.iterator(); 450// while (iter.hasNext()) { 451// HullModSpecAPI curr = iter.next(); 452// for (String tag : tags) { 453// boolean not = tag.startsWith("!"); 454// tag = not ? tag.substring(1) : tag; 455// boolean has = curr.hasTag(tag); 456// if (not == has) { 457// iter.remove(); 458// break; 459// } 460// } 461// } 462// } 463// 464// WeightedRandomPicker<HullModSpecAPI> picker = new WeightedRandomPicker<HullModSpecAPI>(random); 465// //picker.addAll(specs); 466// for (HullModSpecAPI spec : specs) { 467// picker.add(spec, 1f * spec.getRarity()); 468// } 469// HullModSpecAPI pick = picker.pick(); 470// if (pick == null) { 471// copy.commodity = NOTHING; 472// } else { 473// copy.commodity = MOD_PREFIX + pick.getId(); 474// } 475 } else if (isWeapon()) { 476 List<WeaponSpecAPI> specs = Global.getSettings().getAllWeaponSpecs(); 477 if (tier >= 0) { 478 Iterator<WeaponSpecAPI> iter = specs.iterator(); 479 while (iter.hasNext()) { 480 WeaponSpecAPI curr = iter.next(); 481 if (curr.getTier() != tier) iter.remove(); 482 } 483 } 484 485 if (!tags.isEmpty()) { 486 Iterator<WeaponSpecAPI> iter = specs.iterator(); 487 while (iter.hasNext()) { 488 WeaponSpecAPI curr = iter.next(); 489 for (String tag : tags) { 490 boolean not = tag.startsWith("!"); 491 tag = not ? tag.substring(1) : tag; 492 boolean has = curr.hasTag(tag); 493 if (not == has) { 494 iter.remove(); 495 break; 496 } 497 } 498 } 499 } 500 501 if (weaponType != null || weaponSize != null) { 502 Iterator<WeaponSpecAPI> iter = specs.iterator(); 503 while (iter.hasNext()) { 504 WeaponSpecAPI curr = iter.next(); 505 if ((weaponType != null && curr.getType() != weaponType) || 506 (weaponSize != null && curr.getSize() != weaponSize)) { 507 iter.remove(); 508 } 509 } 510 } 511 512 513 WeightedRandomPicker<WeaponSpecAPI> picker = new WeightedRandomPicker<WeaponSpecAPI>(random); 514 //picker.addAll(specs); 515 for (WeaponSpecAPI spec : specs) { 516 picker.add(spec, 1f * spec.getRarity()); 517 } 518 WeaponSpecAPI pick = picker.pick(); 519 if (pick == null) { 520 copy.commodity = NOTHING; 521 } else { 522 copy.commodity = WEAPON_PREFIX + pick.getWeaponId(); 523 } 524 } else if (isFighterWing()) { 525 List<FighterWingSpecAPI> specs = Global.getSettings().getAllFighterWingSpecs(); 526 Iterator<FighterWingSpecAPI> iter = specs.iterator(); 527 while (iter.hasNext()) { 528 FighterWingSpecAPI curr = iter.next(); 529 if (curr.hasTag(Tags.WING_NO_DROP)) iter.remove(); 530 } 531 if (tier >= 0) { 532 iter = specs.iterator(); 533 while (iter.hasNext()) { 534 FighterWingSpecAPI curr = iter.next(); 535 if (curr.getTier() != tier) iter.remove(); 536 } 537 } 538 539 if (!tags.isEmpty()) { 540 iter = specs.iterator(); 541 while (iter.hasNext()) { 542 FighterWingSpecAPI curr = iter.next(); 543 for (String tag : tags) { 544 boolean not = tag.startsWith("!"); 545 tag = not ? tag.substring(1) : tag; 546 boolean has = curr.hasTag(tag); 547 if (not == has) { 548 iter.remove(); 549 break; 550 } 551 } 552 } 553 } 554 WeightedRandomPicker<FighterWingSpecAPI> picker = new WeightedRandomPicker<FighterWingSpecAPI>(random); 555 //picker.addAll(specs); 556 for (FighterWingSpecAPI spec : specs) { 557 picker.add(spec, 1f * spec.getRarity()); 558 } 559 FighterWingSpecAPI pick = picker.pick(); 560 if (pick == null) { 561 copy.commodity = NOTHING; 562 } else { 563 copy.commodity = FIGHTER_PREFIX + pick.getId(); 564 } 565 } 566 567 568 return copy; 569 } 570 571 @Override 572 public String toString() { 573 return super.toString() + " " + commodity; 574 } 575 576 577} 578 579 580 581 582 583 584 585