001package com.fs.starfarer.api.impl.codex;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.LinkedHashMap;
009import java.util.LinkedHashSet;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013import java.util.UUID;
014
015import java.awt.Color;
016
017import org.json.JSONException;
018
019import com.fs.starfarer.api.Global;
020import com.fs.starfarer.api.ModPlugin;
021import com.fs.starfarer.api.ModSpecAPI;
022import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
023import com.fs.starfarer.api.campaign.CargoStackAPI;
024import com.fs.starfarer.api.campaign.PlanetSpecAPI;
025import com.fs.starfarer.api.campaign.SpecialItemPlugin;
026import com.fs.starfarer.api.campaign.SpecialItemSpecAPI;
027import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
028import com.fs.starfarer.api.campaign.impl.items.MultiBlueprintItemPlugin;
029import com.fs.starfarer.api.characters.MarketConditionSpecAPI;
030import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
031import com.fs.starfarer.api.characters.SkillSpecAPI;
032import com.fs.starfarer.api.combat.DamageType;
033import com.fs.starfarer.api.combat.ShipAPI;
034import com.fs.starfarer.api.combat.ShipAPI.HullSize;
035import com.fs.starfarer.api.combat.ShipHullSpecAPI;
036import com.fs.starfarer.api.combat.ShipHullSpecAPI.ShipTypeHints;
037import com.fs.starfarer.api.combat.ShipSystemSpecAPI;
038import com.fs.starfarer.api.combat.ShipVariantAPI;
039import com.fs.starfarer.api.combat.WeaponAPI.AIHints;
040import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
041import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
042import com.fs.starfarer.api.fleet.FleetMemberAPI;
043import com.fs.starfarer.api.fleet.FleetMemberType;
044import com.fs.starfarer.api.impl.SharedUnlockData;
045import com.fs.starfarer.api.impl.campaign.econ.ResourceDepositsCondition;
046import com.fs.starfarer.api.impl.campaign.econ.impl.InstallableItemEffect;
047import com.fs.starfarer.api.impl.campaign.econ.impl.ItemEffectsRepo;
048import com.fs.starfarer.api.impl.campaign.ids.Abilities;
049import com.fs.starfarer.api.impl.campaign.ids.Commodities;
050import com.fs.starfarer.api.impl.campaign.ids.Conditions;
051import com.fs.starfarer.api.impl.campaign.ids.HullMods;
052import com.fs.starfarer.api.impl.campaign.ids.Industries;
053import com.fs.starfarer.api.impl.campaign.ids.Items;
054import com.fs.starfarer.api.impl.campaign.ids.Planets;
055import com.fs.starfarer.api.impl.campaign.ids.ShipSystems;
056import com.fs.starfarer.api.impl.campaign.ids.Skills;
057import com.fs.starfarer.api.impl.campaign.ids.Tags;
058import com.fs.starfarer.api.impl.combat.threat.EnergyLashActivatedSystem;
059import com.fs.starfarer.api.loading.AbilitySpecAPI;
060import com.fs.starfarer.api.loading.Description;
061import com.fs.starfarer.api.loading.Description.Type;
062import com.fs.starfarer.api.loading.FighterWingSpecAPI;
063import com.fs.starfarer.api.loading.HullModSpecAPI;
064import com.fs.starfarer.api.loading.IndustrySpecAPI;
065import com.fs.starfarer.api.loading.WeaponSlotAPI;
066import com.fs.starfarer.api.loading.WeaponSpecAPI;
067import com.fs.starfarer.api.loading.WingRole;
068import com.fs.starfarer.api.loading.WithSourceMod;
069import com.fs.starfarer.api.ui.CustomPanelAPI;
070import com.fs.starfarer.api.ui.TagDisplayAPI;
071import com.fs.starfarer.api.ui.TooltipMakerAPI;
072import com.fs.starfarer.api.ui.UIPanelAPI;
073import com.fs.starfarer.api.util.CountingMap;
074import com.fs.starfarer.api.util.ListMap;
075import com.fs.starfarer.api.util.Misc;
076
077public class CodexDataV2 {
078
079        public static boolean WITH_GAME_MECHANICS_CAT = false;
080        public static boolean WITH_CUSTOM_EXAMPLE_CAT = false;
081        
082        public static boolean USE_KEY_NAMES_FOR_GALLERY = false;
083        
084        public static CodexEntryPlugin ROOT;
085        public static Map<String, CodexEntryPlugin> ENTRIES = new LinkedHashMap<>();
086        public static Map<String, CodexEntryPlugin> SEEN_STATION_MODULES = new LinkedHashMap<>(); 
087        
088        public static String TAG_EMPTY_MODULE = "empty_module";
089        
090        public static String CAT_ROOT = "root";
091        public static String CAT_SHIPS = "ships";
092        public static String CAT_STATIONS = "stations";
093        public static String CAT_FIGHTERS = "fighters";
094        public static String CAT_WEAPONS = "weapons";
095        public static String CAT_HULLMODS = "hullmods";
096        public static String CAT_SHIP_SYSTEMS = "ship_systems";
097        public static String CAT_SPECIAL_ITEMS = "special_items";
098        public static String CAT_INDUSTRIES = "industries";
099        public static String CAT_STARS_AND_PLANETS = "stars_and_planets";
100        public static String CAT_PLANETARY_CONDITIONS = "conditions";
101        public static String CAT_COMMODITIES = "commodities";
102        public static String CAT_GALLERY = "gallery";
103        public static String CAT_SKILLS = "skills";
104        public static String CAT_ABILITIES = "abilities";
105        //public static String CAT_TERRAIN = "terrain";
106        //public static String CAT_FACTIONS = "factions";
107        public static String CAT_GAME_MECHANICS = "game_mechanics";
108        public static String CAT_CUSTOM_EXAMPLE = "custom_example";
109        
110        public static String UNKNOWN_ENTRY_ID = "unknown_entry";
111        
112        
113        public static Map<String, Float> CAT_SORT_RELATED_ENTRIES = new HashMap<>();
114        static {
115                //CAT_SORT_RELATED_ENTRIES.put(CAT_FACTIONS, -10f);
116                //CAT_SORT_RELATED_ENTRIES.put(CAT_FACTIONS, 500f);
117                CAT_SORT_RELATED_ENTRIES.put(CAT_SHIP_SYSTEMS, 0f);
118                CAT_SORT_RELATED_ENTRIES.put(CAT_HULLMODS, 10f);
119                CAT_SORT_RELATED_ENTRIES.put(CAT_WEAPONS, 20f);
120                CAT_SORT_RELATED_ENTRIES.put(CAT_FIGHTERS, 30f);
121                CAT_SORT_RELATED_ENTRIES.put(CAT_SHIPS, 40f);
122                CAT_SORT_RELATED_ENTRIES.put(CAT_STATIONS, 45f);
123                
124                CAT_SORT_RELATED_ENTRIES.put(CAT_SKILLS, 50f);
125                CAT_SORT_RELATED_ENTRIES.put(CAT_ABILITIES, 60f);
126                
127                // TODO: rearrange these as needed depending on where they show up as related
128                CAT_SORT_RELATED_ENTRIES.put(CAT_INDUSTRIES, 100f);
129                CAT_SORT_RELATED_ENTRIES.put(CAT_SPECIAL_ITEMS, 110f);
130                CAT_SORT_RELATED_ENTRIES.put(CAT_STARS_AND_PLANETS, 120f);
131                CAT_SORT_RELATED_ENTRIES.put(CAT_COMMODITIES, 130f);
132                CAT_SORT_RELATED_ENTRIES.put(CAT_PLANETARY_CONDITIONS, 140f);
133                //CAT_SORT_RELATED_ENTRIES.put(CAT_TERRAIN, 145f);
134                CAT_SORT_RELATED_ENTRIES.put(CAT_GAME_MECHANICS, 150f);
135                CAT_SORT_RELATED_ENTRIES.put(CAT_GALLERY, 160f);
136                
137        }
138        
139        
140        public static String ALL_TECHS = "All designs";
141        public static String ALL_APTITUDES = "All aptitudes";
142        public static String ALL_SIZES = "All sizes";
143        public static String ALL_TYPES = "All types";
144        public static String ALL_DAMAGE_TYPES = "All damage";
145        public static String HIGH_EXPLOSIVE = "High explosive";
146        public static String KINETIC = "Kinetic";
147        public static String DAM_ENERGY = "_Energy"; // leadiner _ gets removed from tag name because HACKS
148        public static String FRAGMENTATION = "Fragmentation";
149        //public static String OTHER = "Other";
150        
151        public static String FRIGATES = "Frigates";
152        public static String DESTROYERS = "Destroyers";
153        public static String CRUISERS = "Cruisers";
154        public static String CAPITALS = "Capitals";
155        
156        public static String COMBAT_SHIPS = "Warships";
157        public static String PHASE_SHIPS = "Phase ships";
158        public static String CARRIERS = "Carriers";
159        public static String CIVILIAN = "Civilian";
160        
161        public static String SMALL = "Small";
162        public static String MEDIUM = "Medium";
163        public static String LARGE = "Large";
164        public static String FIGHTER_WEAPON = "Fighter";
165        
166        public static String BALLISTIC = "Ballistic";
167        public static String MISSILE = "Missile";
168        public static String ENERGY = "Energy";
169        public static String HYBRID = "Hybrid";
170        public static String COMPOSITE = "Composite";
171        public static String SYNERGY = "Synergy";
172        public static String UNIVERSAL = "Universal";
173        public static String BEAM = "Beam";
174        
175        public static String FIGHTER = "Fighters";
176        public static String BOMBER = "Bombers";
177        public static String INTERCEPTOR = "Interceptors";
178        public static String OTHER = "Other";
179        
180        public static String DMODS = "D-mods";
181        public static String INTRINSIC = "Intrinsic";
182        
183        public static String PLANETS = "Planets";
184        public static String STARS = "Stars";
185        public static String GAS_GIANTS = "Gas giants";
186        public static String HABITABLE = "Habitable";
187        
188        public static String COLONY = "Colony items";
189        public static String AI_CORE = "AI cores";
190        public static String BLUEPRINTS  = "Blueprint packages";
191        
192        public static String INDUSTRIES = "Industries";
193        public static String STRUCTURES = "Structures";
194        public static String STATIONS = "Stations";
195        
196        public static String RESOURCES = "Resources";
197        
198        public static String PILOTED_SHIP = "Piloted ship";
199        
200        
201        public static void init() {
202                ROOT = new CodexEntryV2("root", "Codex categories", null);
203                ROOT.setRetainOrderOfChildren(true);
204                
205                for (ModPlugin plugin : Global.getSettings().getModManager().getEnabledModPlugins()) {
206                        plugin.onAboutToStartGeneratingCodex();
207                }
208                
209                CodexEntryPlugin ships = createShipsCategory();
210                CodexEntryPlugin stations = createStationsCategory();
211                CodexEntryPlugin fighters = createFightersCategory();
212                CodexEntryPlugin weapons = createWeaponsCategory();
213                CodexEntryPlugin hullmods = createHullModsCategory();
214                CodexEntryPlugin shipSystems = createShipSystemsCategory();
215                
216                CodexEntryPlugin items = createSpecialItemsCategory();
217                CodexEntryPlugin industries = createIndustriesCategory();
218                CodexEntryPlugin planets = createStarsAndPlanetsCategory();
219                CodexEntryPlugin conditions = createPlanetaryConditionsCategory();
220                CodexEntryPlugin commodities = createCommoditiesCategory();
221                //CodexEntryPlugin terrain = createTerrainCategory();
222                //CodexEntryPlugin factions = createFactionsCategory();
223                CodexEntryPlugin gallery = createGalleryCategory();
224                
225                CodexEntryPlugin skills = createSkillsCategory();
226                CodexEntryPlugin abilities = createAbilitiesCategory();
227                
228                ROOT.addChild(ships);
229                //ships.addChild(stations);
230                ROOT.addChild(stations);
231                ROOT.addChild(fighters);
232                ROOT.addChild(weapons);
233                ROOT.addChild(hullmods);
234                ROOT.addChild(shipSystems);
235                ROOT.addChild(items);
236                ROOT.addChild(industries);
237                ROOT.addChild(commodities);
238                ROOT.addChild(planets);
239                ROOT.addChild(conditions);
240                //ROOT.addChild(terrain);
241                ROOT.addChild(skills);
242                ROOT.addChild(abilities);
243                //ROOT.addChild(factions);
244                ROOT.addChild(gallery);
245                
246                if (WITH_GAME_MECHANICS_CAT) {
247                        CodexEntryPlugin mechanics = createGameMechanicsCategory();
248                        ROOT.addChild(mechanics);
249                }
250                
251                rebuildIdToEntryMap();
252                
253                setCatSort(ROOT, null);
254                
255                populateShipsAndStations(ships, stations);
256                //populateStations(ships);
257                populateShipSystems(shipSystems, ships);
258                populateHullMods(hullmods);
259                populateWeapons(weapons);
260                populateFighters(fighters);
261                populateStarsAndPlanets(planets);
262                populatePlanetaryConditions(conditions);
263                populateSpecialItems(items);
264                populateCommodities(commodities, items);
265                populateIndustries(industries);
266                populateSkills(skills);
267                populateAbilities(abilities);
268                //populateTerrain(terrain);
269                //populateFactions(factions);
270                populateGallery(gallery);
271                addUnknownEntry();
272                
273                if (WITH_GAME_MECHANICS_CAT) {
274                        CodexTextEntryLoader.loadTextEntries();
275                }
276                
277                rebuildIdToEntryMap();
278                
279                for (ModPlugin plugin : Global.getSettings().getModManager().getEnabledModPlugins()) {
280                        plugin.onAboutToLinkCodexEntries();
281                }
282                
283                sortSkillsCategory();
284                
285                linkRelatedEntries();
286                if (WITH_GAME_MECHANICS_CAT) {
287                        CodexTextEntryLoader.linkRelated();
288                }
289                
290                if (WITH_CUSTOM_EXAMPLE_CAT) {
291                        addCustomCodexEntryDetailPanelExample();
292                }
293                
294                for (ModPlugin plugin : Global.getSettings().getModManager().getEnabledModPlugins()) {
295                        plugin.onCodexDataGenerated();
296                }
297        }
298        
299        public static void addUnknownEntry() {
300                ROOT.addChild(new CodexEntryV2(UNKNOWN_ENTRY_ID, 
301                                "UNKNOWN ENTRY", null, UNKNOWN_ENTRY_ID) {
302                        @Override
303                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
304                                info.addPara("UNKNOWN ENTRY", Misc.getBasePlayerColor(), 0f);
305                        }
306                        @Override
307                        public boolean isVisible() {
308                                return false;
309                        }
310                        @Override
311                        public boolean isLocked() {
312                                return false;
313                        }
314                        @Override
315                        public boolean hasCustomDetailPanel() {
316                                return true;
317                        }
318                        @Override
319                        public void createCustomDetail(CustomPanelAPI panel, UIPanelAPI relatedEntries, CodexDialogAPI codex) {
320                                float opad = 10f;
321                                float width = panel.getPosition().getWidth();
322                                float horzBoxPad = 30f;
323                                // the right width for a tooltip wrapped in a box to fit next to relatedEntries
324                                float tw = width - 290f - opad - horzBoxPad + 10f;
325                                
326                                TooltipMakerAPI text = panel.createUIElement(tw, 0, false);
327                                text.setParaSmallInsignia();
328                                text.addPara("UNKNOWN ENTRY\n\nPlease register your Tri-Tachyon datapad to receive the latest news and updates.", 0f);
329                                panel.updateUIElementSizeAndMakeItProcessInput(text);
330                                UIPanelAPI box = panel.wrapTooltipWithBox(text);
331                                panel.addComponent(box).inTL(0f, 0f);
332                                if (relatedEntries != null) {
333                                        panel.addComponent(relatedEntries).inTR(0f, 0f);
334                                }
335                                
336                                float height = box.getPosition().getHeight();
337                                if (relatedEntries != null) {
338                                        height = Math.max(height, relatedEntries.getPosition().getHeight());
339                                }
340                        }                       
341                });             
342        }
343        
344        public static void addCustomCodexEntryDetailPanelExample() {
345                if (Global.getSettings().isDevMode() && !Global.getSettings().getBoolean("playtestingMode")) {
346                        CodexEntryPlugin custom = createCustomExampleCategory();
347                        ROOT.addChild(custom);
348                        
349                        CodexCustomEntryExample entry = new CodexCustomEntryExample("custom_example_1", 
350                                                                                "Custom detail example", getIcon(CAT_CUSTOM_EXAMPLE));
351                        entry.setParam("Has to be set to something for detail to show; "
352                                        + "usually this is the data for what the entry is about");
353                        // or, can set it to a fighter wing/hull/weapon spec to get a custom icon
354                        entry.setParam(Global.getSettings().getHullSpec("paragon"));
355                        custom.addChild(entry);
356                        entry.addRelatedEntry(getEntry(getHullmodEntryId(HullMods.ADVANCED_TARGETING_CORE)));
357                        entry.addRelatedEntry(getEntry(getShipEntryId("onslaught")));
358                        entry.addRelatedEntry(getEntry(getWeaponEntryId("autopulse")));
359                        entry.addRelatedEntry(getEntry(getFighterEntryId("wasp_wing")));
360                }
361        }
362
363        public static void setCatSort(CodexEntryPlugin root, Set<CodexEntryPlugin> seen) {
364                if (seen == null) seen = new LinkedHashSet<>();
365                if (seen.contains(root)) return;
366                seen.add(root);
367                for (CodexEntryPlugin cat : root.getChildren()) {
368                        if (!cat.isCategory()) continue;
369                        Float sort = CAT_SORT_RELATED_ENTRIES.get(cat.getId());
370                        if (sort == null) sort = 1000f;
371                        cat.setCategorySortTierForRelatedEntries(sort);
372                        setCatSort(cat, seen);
373                }
374        }
375        
376        public static CodexEntryV2 createHullModsCategory() {
377                CodexEntryV2 hullmods = new CodexEntryV2(CAT_HULLMODS, "Hullmods", getIcon(CAT_HULLMODS)) {
378                        @Override
379                        public boolean hasTagDisplay() {
380                                return true;
381                        }
382                        @Override
383                        public void configureTagDisplay(TagDisplayAPI tags) {
384                                CountingMap<String> techs = new CountingMap<String>();
385                                CountingMap<String> types = new CountingMap<String>();
386                                int dmods = 0;
387                                int intrinsic = 0;
388                                int total = 0;
389                                
390                                for (CodexEntryPlugin curr : getChildren()) {
391                                        if (!(curr.getParam() instanceof HullModSpecAPI)) continue;
392                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
393                                        HullModSpecAPI spec = (HullModSpecAPI) curr.getParam();
394                                        
395                                        if (spec.hasTag(Tags.HULLMOD_DMOD)) {
396                                                dmods++;
397                                        } else if (spec.isHidden()) {
398                                                intrinsic++;
399                                        }
400                                
401                                        techs.add(spec.getManufacturer());
402                                        for (String type : spec.getUITags()) {
403                                                types.add(type);
404                                        }
405                                        total++;
406                                }
407                                
408                                if (dmods > 0) types.add(DMODS, dmods);
409                                if (intrinsic > 0) types.add(INTRINSIC, intrinsic);
410
411                                float opad = 10f;
412                                float pad = 3f;
413                                
414                                tags.beginGroup(false, ALL_TECHS);
415                                List<String> keys = new ArrayList<String>(techs.keySet());
416                                Collections.sort(keys);
417                                for (String tech : keys) {
418                                        tags.addTag(tech, tech, techs.getCount(tech));
419                                }
420                                tags.setTotalOverrideForCurrentGroup(total);
421                                tags.addGroup(0f);
422                                
423                                
424                                tags.beginGroup(false, ALL_TYPES);
425                                keys = new ArrayList<String>(types.keySet());
426                                Collections.sort(keys);
427                                for (String type : keys) {
428                                        tags.addTag(type, type, types.getCount(type));
429                                }
430                                tags.setTotalOverrideForCurrentGroup(total);
431                                tags.addGroup(opad * 1f);
432                                
433                                tags.checkAll();
434                        }
435                        
436                };
437                return hullmods;
438        }
439        
440        public static CodexEntryV2 createGameMechanicsCategory() {
441                CodexEntryV2 cat = new CodexEntryV2(CAT_GAME_MECHANICS, "Spacer's Manual", getIcon(CAT_GAME_MECHANICS)) {
442                        @Override
443                        public boolean hasCustomDetailPanel() {
444                                return true;
445                        }
446                        
447                        @Override
448                        public boolean hasDetail() {
449                                return true;
450                        }
451
452                        @Override
453                        public void createCustomDetail(CustomPanelAPI panel, UIPanelAPI relatedEntries, CodexDialogAPI codex) {
454                                Color color = Misc.getBasePlayerColor();
455                                Color dark = Misc.getDarkPlayerColor();
456                                Color h = Misc.getHighlightColor();
457                                Color g = Misc.getGrayColor();
458                                float opad = 10f;
459                                float pad = 3f;
460                                float small = 5f;
461                                
462                                float width = panel.getPosition().getWidth();
463                                
464                                float initPad = 0f;
465                                
466                                float horzBoxPad = 30f;
467                                
468                                // the right width for a tooltip wrapped in a box to fit next to relatedEntries
469                                // 290 is the width of the related entries widget, but it may be null
470                                float tw = width - 290f - opad - horzBoxPad + 10f;
471                                
472                                TooltipMakerAPI text = panel.createUIElement(tw, 0, false);
473                                text.setParaSmallInsignia();
474                        
475                                text.addPara("Spacer's manual description test.", 0f);
476                                
477                                panel.updateUIElementSizeAndMakeItProcessInput(text);
478                                
479                                UIPanelAPI box = panel.wrapTooltipWithBox(text);
480                                panel.addComponent(box).inTL(0f, 0f);
481                                if (relatedEntries != null) {
482                                        panel.addComponent(relatedEntries).inTR(0f, 0f);
483                                }
484                                
485                                float height = box.getPosition().getHeight();
486                                if (relatedEntries != null) {
487                                        height = Math.max(height, relatedEntries.getPosition().getHeight());
488                                }
489                                panel.getPosition().setSize(width, height);
490                        }                       
491                };
492                
493                return cat;
494        }
495        public static CodexEntryV2 createGalleryCategory() {
496                CodexEntryV2 cat = new CodexEntryV2(CAT_GALLERY, "Gallery", getIcon(CAT_GALLERY));
497                return cat;
498        }
499        public static CodexEntryV2 createCommoditiesCategory() {
500                CodexEntryV2 cat = new CodexEntryV2(CAT_COMMODITIES, "Commodities", getIcon(CAT_COMMODITIES)) {
501                        
502                };
503                return cat;
504        }
505        
506//      public static CodexEntryV2 createTerrainCategory() {
507//              CodexEntryV2 cat = new CodexEntryV2(CAT_TERRAIN, "Terrain", getIcon(CAT_TERRAIN)) {
508//                      
509//              };
510//              return cat;
511//      }
512        
513        public static CodexEntryV2 createIndustriesCategory() {
514                CodexEntryV2 cat = new CodexEntryV2(CAT_INDUSTRIES, "Industries", getIcon(CAT_INDUSTRIES)) {
515                        @Override
516                        public boolean hasTagDisplay() {
517                                return true;
518                        }
519                        @Override
520                        public void configureTagDisplay(TagDisplayAPI tags) {
521                                int industry = 0;
522                                int structure = 0;
523                                int station = 0;
524                                int other = 0;
525                                int total = 0;
526                                for (CodexEntryPlugin curr : getChildren()) {
527                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
528                                        if (!(curr.getParam() instanceof IndustrySpecAPI)) continue;
529                                        
530                                        IndustrySpecAPI spec = (IndustrySpecAPI) curr.getParam();
531                                        if (spec.hasTag(Industries.TAG_INDUSTRY)) industry++;
532                                        else if (spec.hasTag(Industries.TAG_STATION)) station++;
533                                        else if (spec.hasTag(Industries.TAG_STRUCTURE)) structure++;
534                                        else other++;
535                                        
536                                        total++;
537                                }
538                                tags.beginGroup(false, ALL_TYPES);
539                                tags.addTag(INDUSTRIES, industry);
540                                tags.addTag(STRUCTURES, structure);
541                                if (station > 0) tags.addTag(STATIONS, station);
542                                if (other > 0) tags.addTag(OTHER, other);
543                                tags.setTotalOverrideForCurrentGroup(total);
544                                tags.addGroup(0f);
545                                
546                                tags.checkAll();
547                        }
548                };
549                return cat;
550        }
551        public static CodexEntryV2 createSpecialItemsCategory() {
552                CodexEntryV2 cat = new CodexEntryV2(CAT_SPECIAL_ITEMS, "Special items", getIcon(CAT_SPECIAL_ITEMS)) {
553                        @Override
554                        public boolean hasTagDisplay() {
555                                return true;
556                        }
557                        @Override
558                        public void configureTagDisplay(TagDisplayAPI tags) {
559                                int colony = 0;
560                                int core = 0;
561                                int bp = 0;
562                                int other = 0;
563                                int total = 0;
564                                for (CodexEntryPlugin curr : getChildren()) {
565                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
566                                        if (curr.getParam() instanceof SpecialItemSpecAPI) {
567                                                SpecialItemSpecAPI spec = (SpecialItemSpecAPI) curr.getParam();
568                                                if (spec.hasTag(Items.TAG_COLONY_ITEM)) colony++;
569                                                else if (spec.hasTag(Items.TAG_BLUEPRINT_PACKAGE)) bp++;
570                                                else other++;
571                                        } else if (curr.getParam() instanceof CommoditySpecAPI) {
572                                                CommoditySpecAPI spec = (CommoditySpecAPI) curr.getParam();
573                                                if (spec.hasTag(Commodities.TAG_AI_CORE)) core++;
574                                                else other++;
575                                        }
576                                        total++;
577                                }
578                                tags.beginGroup(false, ALL_TYPES);
579                                if (colony > 0) tags.addTag(COLONY, colony);
580                                if (bp > 0) tags.addTag(BLUEPRINTS, bp);
581                                if (core > 0) tags.addTag(AI_CORE, core);
582                                if (other > 0) tags.addTag(OTHER, other);
583                                tags.setTotalOverrideForCurrentGroup(total);
584                                tags.addGroup(0f);
585                                
586                                tags.checkAll();
587                        }
588                };
589                return cat;
590        }
591        public static CodexEntryV2 createPlanetaryConditionsCategory() {
592                CodexEntryV2 cat = new CodexEntryV2(CAT_PLANETARY_CONDITIONS, "Planetary conditions", getIcon(CAT_PLANETARY_CONDITIONS)) {
593                        @Override
594                        public boolean hasTagDisplay() {
595                                return true;
596                        }
597                        @Override
598                        public void configureTagDisplay(TagDisplayAPI tags) {
599                                int resource = 0;
600                                int other = 0;
601                                int total = 0;
602                                for (CodexEntryPlugin curr : getChildren()) {
603                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
604                                        if (!(curr.getParam() instanceof MarketConditionSpecAPI)) continue;
605                                        MarketConditionSpecAPI spec = (MarketConditionSpecAPI) curr.getParam();
606                                        if (ResourceDepositsCondition.COMMODITY.containsKey(spec.getId())) resource++;
607                                        else other++;
608                                        
609                                        total++;
610                                }
611                                tags.beginGroup(false, ALL_TYPES);
612                                tags.addTag(RESOURCES, resource);
613                                tags.addTag(OTHER, other);
614                                tags.setTotalOverrideForCurrentGroup(total);
615                                tags.addGroup(0f);
616                                
617                                tags.checkAll();
618                        }
619                };
620                return cat;
621        }
622        
623        public static CodexEntryV2 createStarsAndPlanetsCategory() {
624                CodexEntryV2 cat = new CodexEntryV2(CAT_STARS_AND_PLANETS, "Stars & planets", getIcon(CAT_STARS_AND_PLANETS)) {
625                        @Override
626                        public boolean hasTagDisplay() {
627                                return true;
628                        }
629                        @Override
630                        public void configureTagDisplay(TagDisplayAPI tags) {
631                                int gasGiants = 0;
632                                int planets = 0;
633                                int stars = 0;
634                                int habitable = 0;
635                                int total = 0;
636                                
637                                for (CodexEntryPlugin curr : getChildren()) {
638                                        if (!(curr.getParam() instanceof PlanetSpecAPI)) continue;
639                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
640                                        
641                                        PlanetSpecAPI spec = (PlanetSpecAPI) curr.getParam();
642                                        if (Misc.canPlanetTypeRollHabitable(spec)) {
643                                                habitable++;
644                                        }
645                                        if (spec.isGasGiant()) {
646                                                gasGiants++;
647                                        } else if (spec.isStar()) {
648                                                stars++;
649                                        } else {
650                                                planets++;
651                                        }
652                                        
653                                        total++;
654                                }
655                                
656                                float opad = 10f;
657                                float pad = 3f;
658                                
659                                tags.beginGroup(false, ALL_TYPES, 120f);
660                                tags.addTag(STARS, stars);
661                                tags.addTag(GAS_GIANTS, gasGiants);
662                                tags.addTag(PLANETS, planets);
663                                tags.addTag(HABITABLE, habitable);
664                                tags.setTotalOverrideForCurrentGroup(total);
665                                tags.addGroup(opad);
666                                
667                                tags.checkAll();
668                        }
669                };
670                return cat;
671        }
672        
673        public static CodexEntryV2 createShipSystemsCategory() {
674                CodexEntryV2 shipSystems = new CodexEntryV2(CAT_SHIP_SYSTEMS, "Ship systems", getIcon(CAT_SHIP_SYSTEMS)) {
675                        @Override
676                        public boolean hasTagDisplay() {
677                                return true;
678                        }
679                        @Override
680                        public void configureTagDisplay(TagDisplayAPI tags) {
681                                int total = 0;
682                                CountingMap<String> types = new CountingMap<>();
683                                for (CodexEntryPlugin curr : getChildren()) {
684                                        if (!(curr.getParam() instanceof ShipSystemSpecAPI)) continue;
685                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
686                                        
687                                        ShipSystemSpecAPI spec = (ShipSystemSpecAPI) curr.getParam();
688                                        
689                                        Description desc = Global.getSettings().getDescription(spec.getId(), Type.SHIP_SYSTEM);
690                                        if (desc.hasText2()) {
691                                                String typeStr = desc.getText2();
692                                                types.add(typeStr);
693                                        } else {
694                                                types.add("Special");
695                                        }
696                                        
697                                        total++;
698                                }
699                                
700                                tags.beginGroup(false, ALL_TYPES);
701                                List<String> keys = new ArrayList<String>(types.keySet());
702                                Collections.sort(keys);
703                                for (String type : keys) {
704                                        tags.addTag(type, type, types.getCount(type));
705                                }
706                                tags.setTotalOverrideForCurrentGroup(total);
707                                tags.addGroup(0f);
708                                
709                                tags.checkAll();
710                        }
711                };
712                return shipSystems;
713        }
714        
715        public static CodexEntryV2 createFightersCategory() {
716                CodexEntryV2 fighters = new CodexEntryV2(CAT_FIGHTERS, "Fighters", getIcon(CAT_FIGHTERS)) {
717                        @Override
718                        public boolean hasTagDisplay() {
719                                return true;
720                        }
721                        @Override
722                        public void configureTagDisplay(TagDisplayAPI tags) {
723                                int fighter = 0;
724                                int bomber = 0;
725                                int interceptor = 0;
726                                int other = 0;
727                                int total = 0;
728                                
729                                CountingMap<String> techs = new CountingMap<String>();
730                                for (CodexEntryPlugin curr : getChildren()) {
731                                        if (!(curr.getParam() instanceof FighterWingSpecAPI)) continue;
732                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
733                                        
734                                        FighterWingSpecAPI spec = (FighterWingSpecAPI) curr.getParam();
735                                        techs.add(spec.getVariant().getHullSpec().getManufacturer());
736                                        if (spec.getRole() == WingRole.FIGHTER) fighter++;
737                                        else if (spec.getRole() == WingRole.BOMBER) bomber++;
738                                        else if (spec.getRole() == WingRole.INTERCEPTOR) interceptor++;
739                                        else other++;
740                                        
741                                        total++;
742                                }
743                                
744                                float opad = 10f;
745                                float pad = 3f;
746                                
747                                if (!techs.isEmpty()) {
748                                        tags.beginGroup(false, ALL_TECHS);
749                                        List<String> keys = new ArrayList<String>(techs.keySet());
750                                        Collections.sort(keys);
751                                        for (String tech : keys) {
752                                                tags.addTag(tech, tech, techs.getCount(tech));
753                                        }
754                                        tags.setTotalOverrideForCurrentGroup(total);
755                                        tags.addGroup(0f);
756                                }
757                                
758                                tags.beginGroup(false, ALL_TYPES, 120f);
759                                tags.addTag(FIGHTER, fighter);
760                                tags.addTag(BOMBER, bomber);
761                                tags.addTag(INTERCEPTOR, interceptor);
762                                tags.addTag(OTHER, other);
763                                tags.setTotalOverrideForCurrentGroup(total);
764                                tags.addGroup(opad);
765                                
766                                tags.checkAll();
767                        }
768                };
769                return fighters;
770        }
771        
772        public static CodexEntryV2 createCustomExampleCategory() {
773                CodexEntryV2 custom = new CodexEntryV2(CAT_CUSTOM_EXAMPLE, "Custom example", getIcon(CAT_CUSTOM_EXAMPLE));
774                return custom;
775        }
776        
777        public static CodexEntryV2 createWeaponsCategory() {
778                CodexEntryV2 weapons = new CodexEntryV2(CAT_WEAPONS, "Weapons", getIcon(CAT_WEAPONS)) {
779                        @Override
780                        public boolean hasTagDisplay() {
781                                return true;
782                        }
783                        @Override
784                        public void configureTagDisplay(TagDisplayAPI tags) {
785                                int small = 0;
786                                int medium = 0;
787                                int large = 0;
788                                int fighter = 0;
789                                int ballistic = 0;
790                                int missile = 0;
791                                int energy = 0;
792                                int hybrid = 0;
793                                int composite = 0;
794                                int synergy = 0;
795                                int universal = 0;
796                                int beam = 0;
797                                int damHE = 0;
798                                int damKinetic = 0;
799                                int damEnergy = 0;
800                                int damFrag = 0;
801                                int damOther = 0;
802                                int total = 0;
803                                
804                                CountingMap<String> techs = new CountingMap<String>();
805                                for (CodexEntryPlugin curr : getChildren()) {
806                                        if (!(curr.getParam() instanceof WeaponSpecAPI)) continue;
807                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
808                                        
809                                        WeaponSpecAPI spec = (WeaponSpecAPI) curr.getParam();
810                                        techs.add(spec.getManufacturer());
811                                        
812                                        if (spec.getAIHints().contains(AIHints.SYSTEM) &&
813                                                        spec.getPrimaryRoleStr() != null &&
814                                                        spec.getPrimaryRoleStr().endsWith("(Fighter)")) {
815                                                fighter++;
816                                        } else {
817                                                if (spec.getSize() == WeaponSize.SMALL) small++;
818                                                else if (spec.getSize() == WeaponSize.MEDIUM) medium++;
819                                                else if (spec.getSize() == WeaponSize.LARGE) large++;
820                                        }
821                                        
822                                        if (spec.getType() == WeaponType.BALLISTIC) ballistic++;
823                                        else if (spec.getType() == WeaponType.MISSILE) missile++;
824                                        else if (spec.getType() == WeaponType.ENERGY) energy++;
825                                        
826                                        if (spec.getMountType() == WeaponType.HYBRID) hybrid++;
827                                        else if (spec.getMountType() == WeaponType.COMPOSITE) composite++;
828                                        else if (spec.getMountType() == WeaponType.SYNERGY) synergy++;
829                                        else if (spec.getMountType() == WeaponType.UNIVERSAL) universal++;
830                                        
831                                        if (spec.getDamageType() == DamageType.HIGH_EXPLOSIVE) damHE++;
832                                        else if (spec.getDamageType() == DamageType.KINETIC) damKinetic++;
833                                        else if (spec.getDamageType() == DamageType.ENERGY) damEnergy++;
834                                        else if (spec.getDamageType() == DamageType.FRAGMENTATION) damFrag++;
835                                        else if (spec.getDamageType() == DamageType.OTHER) damOther++;
836                                        
837                                        if (spec.isBeam()) beam++;
838                                        
839                                        total++;
840                                }
841                                
842                                float opad = 10f;
843                                float pad = 3f;
844                                
845                                if (!techs.isEmpty()) {
846                                        tags.beginGroup(false, ALL_TECHS);
847                                        List<String> keys = new ArrayList<String>(techs.keySet());
848                                        Collections.sort(keys);
849                                        for (String tech : keys) {
850                                                tags.addTag(tech, tech, techs.getCount(tech));
851                                        }
852                                        tags.setTotalOverrideForCurrentGroup(total);
853                                        tags.addGroup(0f);
854                                }
855                                
856                                tags.beginGroup(false, ALL_SIZES, 120f);
857                                tags.addTag(SMALL, small);
858                                tags.addTag(MEDIUM, medium);
859                                tags.addTag(LARGE, large);
860                                if (fighter > 0) tags.addTag(FIGHTER_WEAPON, fighter);
861                                tags.setTotalOverrideForCurrentGroup(total);
862                                tags.addGroup(opad * 1f);
863                                
864                                tags.beginGroup(false, ALL_TYPES, 120f);
865                                tags.addTag(BALLISTIC, ballistic);
866                                tags.addTag(MISSILE, missile);
867                                tags.addTag(ENERGY, energy);
868                                if (hybrid > 0) tags.addTag(HYBRID, hybrid);
869                                if (composite > 0) tags.addTag(COMPOSITE, composite);
870                                if (synergy > 0) tags.addTag(SYNERGY, synergy);
871                                if (universal > 0) tags.addTag(UNIVERSAL, universal);
872                                if (beam > 0) tags.addTag(BEAM, beam);
873                                tags.setTotalOverrideForCurrentGroup(total);
874                                tags.addGroup(pad);
875                                
876                                tags.beginGroup(false, ALL_DAMAGE_TYPES, 140f);
877                                tags.addTag(HIGH_EXPLOSIVE, damHE);
878                                tags.addTag(KINETIC, damKinetic);
879                                tags.addTag(FRAGMENTATION, damFrag);
880                                tags.addTag(DAM_ENERGY, damEnergy);
881                                if (damOther > 0) tags.addTag(OTHER, damOther);
882                                tags.setTotalOverrideForCurrentGroup(total);
883                                tags.addGroup(pad);
884                                
885                                tags.checkAll();
886                        }
887                        
888                };
889                return weapons;
890        }
891        
892        public static CodexEntryV2 createSkillsCategory() {
893                CodexEntryV2 skills = new CodexEntryV2(CAT_SKILLS, "Skills", getIcon(CAT_SKILLS)) {
894                        @Override
895                        public boolean hasTagDisplay() {
896                                return true;
897                        }
898                        @Override
899                        public void configureTagDisplay(TagDisplayAPI tags) {
900                                int personal = 0;
901                                int other = 0;
902                                int total = 0;
903                                
904                                CountingMap<String> apts = new CountingMap<String>();
905                                for (CodexEntryPlugin curr : getChildren()) {
906                                        if (!(curr.getParam() instanceof SkillSpecAPI)) continue;
907                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
908                                        
909                                        SkillSpecAPI spec = (SkillSpecAPI) curr.getParam();
910                                        String apt = getAptitudeName(spec);
911                                        if (!apt.isEmpty()) apts.add(apt);
912                                        
913                                        if (spec.isElite()) personal++;
914                                        else other++;
915                                        
916                                        total++;
917                                }
918                                
919                                float opad = 10f;
920                                float pad = 3f;
921                                
922                                if (!apts.isEmpty()) {
923                                        tags.beginGroup(false, ALL_APTITUDES);
924                                        List<String> keys = new ArrayList<String>(apts.keySet());
925                                        Collections.sort(keys);
926                                        for (String tech : keys) {
927                                                tags.addTag(tech, tech, apts.getCount(tech));
928                                        }
929                                        tags.addGroup(0f);
930                                }
931                                
932                                tags.beginGroup(false, ALL_TYPES, 120f);
933                                tags.addTag(PILOTED_SHIP, personal);
934                                tags.addTag(OTHER, other);
935                                tags.setTotalOverrideForCurrentGroup(total);
936                                tags.addGroup(opad);
937                                
938                                tags.checkAll();
939                        }                       
940                };
941                skills.setRetainOrderOfChildren(true);
942                return skills;
943        }
944        
945        public static CodexEntryV2 createAbilitiesCategory() {
946                CodexEntryV2 abilities = new CodexEntryV2(CAT_ABILITIES, "Abilities", getIcon(CAT_ABILITIES));
947                return abilities;
948        }
949        
950        public static CodexEntryV2 createShipsCategory() {
951                CodexEntryV2 ships = new CodexEntryV2(CAT_SHIPS, "Ships", getIcon(CAT_SHIPS)) {
952                        @Override
953                        public boolean hasTagDisplay() {
954                                return true;
955                        }
956                        @Override
957                        public void configureTagDisplay(TagDisplayAPI tags) {
958                                int carrier = 0;
959                                int civilian = 0;
960                                int phase = 0;
961                                int combat = 0;
962                                int frigate = 0;
963                                int destroyer = 0;
964                                int cruiser = 0;
965                                int capital = 0;
966                                int stations = 0;
967                                
968                                int total = 0;
969                                CountingMap<String> counts = new CountingMap<String>();
970                                //for (UITableRow o : shipTable.getRows()) {
971                                for (CodexEntryPlugin curr : getChildren()) {
972                                        if (!(curr.getParam() instanceof ShipHullSpecAPI)) continue;
973                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
974
975                                        boolean station = false;
976                                        //boolean stationModule = false;
977                                        if (curr.getParam2() instanceof FleetMemberAPI) {
978                                                FleetMemberAPI fm = (FleetMemberAPI) curr.getParam2();
979                                                station = fm.isStation();
980                                        }
981                                        
982                                        ShipHullSpecAPI spec = (ShipHullSpecAPI) curr.getParam();
983                                        boolean isPhase = spec.isPhase() || spec.getHints().contains(ShipTypeHints.PHASE);
984                                        
985                                        counts.add(spec.getManufacturer());
986                                        if (spec.isCarrier()) carrier++;
987                                        else if (spec.isCivilianNonCarrier()) civilian++;
988                                        else if (isPhase) phase++;
989                                        else if (!station) combat++;
990                                        
991                                        if (spec.isCivilianNonCarrier() && isPhase) phase++;
992                                        
993                                        if (station) stations++;
994                                        else if (spec.getHullSize() == HullSize.FRIGATE) frigate++;
995                                        else if (spec.getHullSize() == HullSize.DESTROYER) destroyer++;
996                                        else if (spec.getHullSize() == HullSize.CRUISER) cruiser++;
997                                        else if (spec.getHullSize() == HullSize.CAPITAL_SHIP) capital++;
998                                        
999                                        total++;
1000                                }
1001                                
1002                                float opad = 10f;
1003                                float pad = 3f;
1004                                
1005                                if (!counts.isEmpty()) {
1006                                        tags.beginGroup(false, ALL_TECHS);
1007                                        List<String> keys = new ArrayList<String>(counts.keySet());
1008                                        Collections.sort(keys);
1009                                        for (String tech : keys) {
1010                                                tags.addTag(tech, tech, counts.getCount(tech));
1011                                        }
1012                                        tags.addGroup(0f);
1013                                }
1014                                
1015                                tags.beginGroup(false, ALL_SIZES, 120f);
1016                                //tags.beginGroup(true, null, 110f);
1017                                tags.addTag(FRIGATES, frigate);
1018                                tags.addTag(DESTROYERS, destroyer);
1019                                tags.addTag(CRUISERS, cruiser);
1020                                tags.addTag(CAPITALS, capital);
1021                                if (stations > 0) tags.addTag(STATIONS, stations);
1022                                tags.setTotalOverrideForCurrentGroup(total);
1023                                tags.addGroup(opad * 1f);
1024                                
1025                                tags.beginGroup(false, ALL_TYPES, 120f);
1026                                //tags.beginGroup(true, null, 110f);
1027                                tags.addTag(COMBAT_SHIPS, combat);
1028                                tags.addTag(PHASE_SHIPS , phase);
1029                                tags.addTag(CARRIERS, carrier);
1030                                tags.addTag(CIVILIAN, civilian);
1031                                tags.setTotalOverrideForCurrentGroup(total);
1032                                tags.addGroup(pad);
1033                                
1034                                tags.setGroupChecked(0, true);
1035                                tags.setGroupChecked(1, true);
1036                                tags.setGroupChecked(2, true);
1037                        }
1038                        
1039                };
1040                return ships;
1041        }
1042        
1043        public static CodexEntryV2 createStationsCategory() {
1044                CodexEntryV2 stations = new CodexEntryV2(CAT_STATIONS, "Stations", getIcon(CAT_STATIONS)) {
1045                        @Override
1046                        public boolean hasTagDisplay() {
1047                                return true;
1048                        }
1049                        @Override
1050                        public void configureTagDisplay(TagDisplayAPI tags) {
1051                                int total = 0;
1052                                CountingMap<String> counts = new CountingMap<String>();
1053                                //for (UITableRow o : shipTable.getRows()) {
1054                                for (CodexEntryPlugin curr : getChildren()) {
1055                                        if (!(curr.getParam() instanceof ShipHullSpecAPI)) continue;
1056                                        if (!curr.isVisible() || curr.isLocked() || curr.skipForTags()) continue;
1057                                        
1058                                        ShipHullSpecAPI spec = (ShipHullSpecAPI) curr.getParam();
1059                                        counts.add(spec.getManufacturer());
1060                                        total++;
1061                                }
1062                                
1063                                if (!counts.isEmpty()) {
1064                                        tags.beginGroup(false, ALL_TECHS);
1065                                        List<String> keys = new ArrayList<String>(counts.keySet());
1066                                        Collections.sort(keys);
1067                                        for (String tech : keys) {
1068                                                tags.addTag(tech, tech, counts.getCount(tech));
1069                                        }
1070                                        tags.setTotalOverrideForCurrentGroup(total);
1071                                        tags.addGroup(0f);
1072                                }
1073                                tags.checkAll();
1074                        }
1075                        
1076                };
1077                return stations;
1078        }
1079        
1080        public static void populateShipsAndStations(CodexEntryPlugin ships, CodexEntryPlugin stations) {
1081                List<ShipHullSpecAPI> shipSpecs = Global.getSettings().getAllShipHullSpecs();
1082                for (final ShipHullSpecAPI spec : shipSpecs) {
1083                        if (spec.getHullSize() == ShipAPI.HullSize.FIGHTER && !spec.hasTag(Tags.SHOW_IN_CODEX_AS_SHIP)) continue;
1084                        if (spec.getHints().contains(ShipTypeHints.HIDE_IN_CODEX)) continue;
1085                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1086                        if (spec.isDefaultDHull()) continue;
1087                        if (spec.getHullId().equals("shuttlepod")) continue;
1088                        
1089                        boolean station = spec.getHints().contains(ShipTypeHints.STATION);
1090                        String name = spec.getHullNameWithDashClass();
1091                        String designation = "";
1092                        if (spec.hasDesignation() && !spec.getDesignation().equals(spec.getHullName())) {
1093                                designation = " " + spec.getDesignation().toLowerCase();
1094                        }
1095                        if (station) {
1096                                designation = "";
1097                                name = spec.getHullName();
1098                        }
1099                        CodexEntryV2 curr = new CodexEntryV2(getShipEntryId(spec.getHullId()), 
1100                                spec.getHullNameWithDashClass() + designation, null, spec) {
1101                                        @Override
1102                                        public String getSortTitle() {
1103                                                return spec.getHullName();
1104                                        }
1105                                        @Override
1106                                        public String getSearchString() {
1107                                                if (station) {
1108                                                        return super.getSearchString() + " Station";
1109                                                }
1110                                                return super.getSearchString();
1111                                        }
1112                                        @Override
1113                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1114                                                info.addPara(spec.getHullName(), Misc.getBasePlayerColor(), 0f);
1115                                                if (station) {
1116                                                        if (mode == ListMode.RELATED_ENTRIES) {
1117                                                                info.addPara("Station", Misc.getGrayColor(), 0f);
1118                                                        }
1119                                                } else if (spec.hasDesignation() && !spec.getDesignation().equals(spec.getHullName())) {
1120                                                        info.addPara(Misc.ucFirst(spec.getDesignation().toLowerCase()), Misc.getGrayColor(), 0f);
1121                                                }
1122                                        }
1123
1124                                        @Override
1125                                        public boolean matchesTags(Set<String> tags) {
1126                                                HullSize size = spec.getHullSize();
1127                                                
1128                                                if (station) {
1129//                                                      String sizeTag = STATIONS;
1130//                                                      if (sizeTag != null && !tags.contains(sizeTag)) return false;
1131        
1132                                                        String m = spec.getManufacturer();
1133                                                        if (m != null && !tags.contains(m) && !tags.contains(ALL_TECHS)) return false;
1134                                                        
1135//                                                      if (!tags.contains(ALL_TYPES)) return false;
1136                                                        
1137                                                        return true;
1138                                                }
1139                                                
1140                                                String sizeTag = null;
1141                                                if (size == HullSize.FRIGATE) sizeTag = FRIGATES;
1142                                                if (size == HullSize.DESTROYER) sizeTag = DESTROYERS;
1143                                                if (size == HullSize.CRUISER) sizeTag = CRUISERS;
1144                                                if (size == HullSize.CAPITAL_SHIP) sizeTag = CAPITALS;
1145                                                
1146                                                if (sizeTag != null && !tags.contains(sizeTag)) return false;
1147
1148                                                boolean isPhase = spec.isPhase() || spec.getHints().contains(ShipTypeHints.PHASE);
1149                                                boolean phaseCiv = isPhase && spec.isCivilianNonCarrier();
1150                                                if (!phaseCiv && isPhase && !tags.contains(PHASE_SHIPS)) return false;
1151                                                if (!phaseCiv && spec.isCivilianNonCarrier() && !tags.contains(CIVILIAN)) return false;
1152                                                if (phaseCiv && !tags.contains(PHASE_SHIPS) && !tags.contains(CIVILIAN)) return false;
1153                                                
1154                                                if (spec.isCarrier() && !tags.contains(CARRIERS)) return false;
1155                                                
1156                                                boolean combat = !isPhase && !spec.isCarrier() && !spec.isCivilianNonCarrier();
1157                                                if (combat && !tags.contains(COMBAT_SHIPS)) return false;
1158                                                
1159                                                String m = spec.getManufacturer();
1160                                                if (m != null && !tags.contains(m) && !tags.contains(ALL_TECHS)) return false;
1161                                                
1162                                                return true;
1163                                        }
1164                                        
1165                                        @Override
1166                                        public Set<String> getUnlockRelatedTags() {
1167                                                return spec.getTags();
1168                                        }
1169                                        
1170                                        @Override
1171                                        public boolean isUnlockedIfRequiresUnlock() {
1172                                                return SharedUnlockData.get().isPlayerAwareOfShip(spec.getHullId());
1173                                        }
1174                        };
1175                        if (station) {
1176                                stations.addChild(curr);
1177                        } else {
1178                                ships.addChild(curr);
1179                        }
1180                
1181                        String variantId = spec.getHullId() + "_Hull";
1182                        if (spec.getCodexVariantId() != null && !spec.getCodexVariantId().isBlank()) {
1183                                variantId = spec.getCodexVariantId();
1184                        }
1185                        ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
1186                        if (variant != null) {
1187                                CodexEntryPlugin parent = ships;
1188                                if (station) parent = stations;
1189                                
1190                                int relBefore = curr.getRelatedEntries().size();
1191                                List<CodexEntryPlugin> added = addModulesForVariant(variant, true, curr, parent);
1192                                // created entries, or they were already created but just added them as related
1193                                if (!added.isEmpty() || relBefore < curr.getRelatedEntryIds().size()) {
1194                                        FleetMemberAPI member = Global.getSettings().createFleetMember(FleetMemberType.SHIP, variant);
1195                                        curr.setParam2(member);
1196                                }
1197                        }
1198                }
1199        }
1200        
1201//  handled by addShipsAndStations now  
1202//      public static void populateStations(CodexEntryPlugin parent) {
1203//              List<IndustrySpecAPI> specs = Global.getSettings().getAllIndustrySpecs();
1204//              for (final IndustrySpecAPI spec : specs) {
1205//                      if (spec.hasTag(Industries.TAG_PARENT)) continue;
1206//                      if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1207//              
1208//                      if (spec.hasTag(Tags.STATION)) {
1209//                              String variantId = null;
1210//                              try {
1211//                                      JSONObject json = new JSONObject(spec.getData());
1212//                                      variantId = json.getString("variant");
1213//                              } catch (JSONException e) {
1214//                                      continue;
1215//                              }
1216//                              
1217//                              if (variantId == null || variantId.isBlank()) continue;
1218//                              
1219//                              FleetMemberAPI member = createStationFleetMember(variantId);
1220//                              addStationEntry(parent, member, true, false);
1221//                      }
1222//              }
1223//              
1224//              addStationEntry(parent, "remnant_station2_Standard", true, false);
1225//              
1226//              SalvageEntityGenDataSpec mothership = SalvageEntityGeneratorOld.getSalvageSpec(Entities.DERELICT_MOTHERSHIP);
1227//              if (mothership != null) {
1228//                      String role = mothership.getStationRole();
1229//                      String factionId = mothership.getDefFaction();
1230//                      FactionSpecAPI faction = Global.getSettings().getFactionSpec(factionId);
1231//                      if (faction != null) {
1232//                              for (String variantId : faction.getAllVariantsForRole(role)) {
1233//                                      addStationEntry(parent, variantId, true, false);
1234//                              }
1235//                      }
1236//              }
1237//      }
1238//      
1239//      public static void addStationEntry(CodexEntryPlugin parent, String variantId, boolean convertToHull, boolean forceAdd) {
1240//              FleetMemberAPI member = createStationFleetMember(variantId);
1241//              addStationEntry(parent, member, convertToHull, forceAdd);
1242//      }
1243//      
1244//      public static FleetMemberAPI createStationFleetMember(String variantId) {
1245//              FleetMemberAPI member = Global.getSettings().createFleetMember(FleetMemberType.SHIP, variantId);
1246//              return member;
1247//      }
1248//      
1249//      public static void addStationEntry(CodexEntryPlugin parent, FleetMemberAPI member, boolean convertToHull, boolean forceAdd) {
1250//              ShipHullSpecAPI spec = member.getHullSpec();
1251//              
1252//              if (!forceAdd && spec.hasTag(Tags.HIDE_STATION_IN_CODEX)) return;
1253//              
1254//              if (convertToHull) {
1255//                      String variantId = getFleetMemberBaseHullId(member) + "_Hull";
1256//                      ShipVariantAPI variant = Global.getSettings().getVariant(variantId).clone();
1257//                      // the variant already has empty _Hull modules, or should
1258//                      if (variant.getStationModules() == null || variant.getStationModules().isEmpty()) {
1259//      //                      for (String slotId : member.getVariant().getModuleSlots()) {
1260//      //                              ShipVariantAPI module = member.getVariant().getModuleVariant(slotId);
1261//      //                              if (module != null) {
1262//      //                                      String moduleBaseHullVariantId = getBaseHullId(module.getHullSpec()) + "_Hull";
1263//      //                                      ShipVariantAPI emptyModuleVariant = Global.getSettings().getVariant(moduleBaseHullVariantId).clone();
1264//      //                                      variant.setModuleVariant(slotId, emptyModuleVariant);
1265//      //                              }
1266//      //                      }
1267//                      }
1268//                      member.setVariant(variant, false, true);
1269//              }
1270//              
1271//                      
1272//              CodexEntryV2 curr = new CodexEntryV2(getShipEntryId(spec.getHullId()), 
1273//                      spec.getHullName(), null, spec) {
1274//                              @Override
1275//                              public String getSortTitle() {
1276//                                      return spec.getHullName();
1277//                              }
1278//                              @Override
1279//                              public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1280//                                      info.addPara(spec.getHullName(), Misc.getBasePlayerColor(), 0f);
1281//                                      info.addPara("Station", Misc.getGrayColor(), 0f);
1282//                              }
1283//
1284//                              @Override
1285//                              public boolean matchesTags(Set<String> tags) {
1286//                                      String sizeTag = STATIONS;
1287//                                      if (sizeTag != null && !tags.contains(sizeTag)) return false;
1288//
1289//                                      String m = spec.getManufacturer();
1290//                                      if (m != null && !tags.contains(m) && !tags.contains(ALL_TECHS)) return false;
1291//                                      
1292//                                      if (!tags.contains(ALL_TYPES)) return false;
1293//                                      
1294//                                      return true;
1295//                              }
1296//                              
1297//                              @Override
1298//                              public String getSearchString() {
1299//                                      return super.getSearchString() + " Station";
1300//                              }
1301//                              @Override
1302//                              public Set<String> getUnlockRelatedTags() {
1303//                                      return spec.getTags();
1304//                              }
1305//                              
1306//                              @Override
1307//                              public boolean isUnlockedIfRequiresUnlock() {
1308//                                      return SharedUnlockData.get().isPlayerAwareOfShip(spec.getHullId());
1309//                              }
1310//              };
1311//              curr.setParam2(member);
1312//              parent.addChild(curr);
1313//              
1314//              if (convertToHull) {
1315//                      curr.addTag(TAG_EMPTY_HULL_WITH_MODULES);
1316//              }
1317//              
1318//              addModulesForVariant(member.getVariant(), convertToHull, curr, parent);
1319//      }
1320        
1321        public static List<CodexEntryPlugin> addModulesForVariant(ShipVariantAPI variant, boolean isEmptyHull,
1322                                                                                        CodexEntryPlugin entry, CodexEntryPlugin parent) {
1323                List<CodexEntryPlugin> result = new ArrayList<>();              
1324                Set<String> seenVariants = new HashSet<>();
1325                for (String slotId : variant.getStationModules().keySet()) {
1326                        ShipVariantAPI moduleVariant = variant.getModuleVariant(slotId);
1327                        if (moduleVariant == null) continue;
1328                        if (moduleVariant.hasHullMod(HullMods.VASTBULK)) continue;
1329
1330                        String moduleEntryId = getShipEntryId(moduleVariant.getHullSpec().getHullId());
1331                        if (isEmptyHull) {
1332                                if (SEEN_STATION_MODULES.containsKey(moduleEntryId) || ENTRIES.containsKey(moduleEntryId)) {
1333                                        makeRelated(entry, SEEN_STATION_MODULES.get(moduleEntryId));
1334                                        continue;
1335                                }
1336                        } else {
1337                                // we're adding an entry for a specific fleet member's variant of the station
1338                                // so, want to add all the modules as fleetmember entries
1339                                // except for ones with duplicate variants
1340                                try {
1341                                        String test = moduleVariant.toJSONObject().toString(4);
1342                                        if (seenVariants.contains(test)) continue;
1343                                        seenVariants.add(test);
1344                                } catch (JSONException e) {
1345                                }
1346                        }
1347                        
1348                        FleetMemberAPI moduleMember = Global.getSettings().createFleetMember(FleetMemberType.SHIP, moduleVariant);
1349                        //if (moduleMember.getVariant().getFittedWeaponSlots().isEmpty()) continue;
1350                        CodexEntryPlugin module = addModuleEntry(parent, entry, moduleMember, isEmptyHull);
1351                        SEEN_STATION_MODULES.put(moduleEntryId, module);
1352                        
1353                        makeRelated(entry, module);
1354                        result.add(module);
1355                        
1356                        if (!isEmptyHull) {
1357                                linkFleetMemberEntryToRelated(module, moduleMember, false);
1358                        }
1359                }
1360                return result;
1361        }
1362        
1363        public static CodexEntryPlugin addModuleEntry(CodexEntryPlugin parent, CodexEntryPlugin entryForParentShip,
1364                                                                                                  FleetMemberAPI member, boolean isEmptyHull) {
1365                ShipHullSpecAPI spec = member.getHullSpec();
1366                        
1367                String moduleEntryId = getShipEntryId(spec.getHullId());
1368                if (!isEmptyHull) { // adding a temp entry
1369                        moduleEntryId = UUID.randomUUID().toString();
1370                }
1371                
1372                CodexEntryV2 curr = new CodexEntryV2(moduleEntryId, 
1373                        spec.getHullName(), null, spec) {
1374                                @Override
1375                                public String getSortTitle() {
1376                                        return spec.getHullName();
1377                                }
1378                                @Override
1379                                public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1380                                        info.addPara(spec.getHullName(), Misc.getBasePlayerColor(), 0f);
1381                                        info.addPara("Module", Misc.getGrayColor(), 0f);
1382                                }
1383                                @Override
1384                                public boolean matchesTags(Set<String> tags) {
1385                                        return false;
1386                                }
1387                                @Override
1388                                public boolean checkTagsWhenLocked() {
1389                                        // so that the module does not show up in the main list when it's locked
1390                                        // despite matchesTags() always being false
1391                                        return true;
1392                                }
1393                                @Override
1394                                public boolean isVisible() {
1395                                        return entryForParentShip.isVisible();
1396                                }
1397                                @Override
1398                                public boolean isLocked() {
1399                                        return entryForParentShip.isLocked();
1400                                }
1401                                @Override
1402                                public String getSearchString() {
1403                                        return "";
1404                                }
1405                                @Override
1406                                public boolean skipForTags() {
1407                                        // actually, not adding it to the parent's children is easier
1408                                        // actually: not easier, it messes up the sort order in the related entries list
1409                                        return true;
1410                                }
1411                };
1412                curr.setParam2(member);
1413                if (isEmptyHull) {
1414                        curr.addTag(TAG_EMPTY_MODULE);
1415                }
1416                if (parent != null) parent.addChild(curr);
1417                return curr;
1418        }
1419        
1420        public static void populateShipSystems(CodexEntryPlugin parent, CodexEntryPlugin ships) {
1421                List<ShipSystemSpecAPI> specs = Global.getSettings().getAllShipSystemSpecs();
1422                for (final ShipSystemSpecAPI spec : specs) {
1423                        //if (spec.getTags().contains(Tags.RESTRICTED)) continue;
1424                        if (spec.getTags().contains(Tags.HIDE_IN_CODEX)) continue;
1425                        
1426                        Description desc = Global.getSettings().getDescription(spec.getId(), Type.SHIP_SYSTEM);
1427                        String typeStr = desc.getText2();
1428                        CodexEntryV2 curr = new CodexEntryV2(getShipSystemEntryId(spec.getId()), 
1429                                spec.getName(), spec.getIconSpriteName(), spec) {
1430                                        @Override
1431                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1432                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
1433                                                if (mode == ListMode.RELATED_ENTRIES) {
1434                                                        info.addPara("Ship system", Misc.getGrayColor(), 0f);
1435                                                } else if (desc.hasText2()) {
1436                                                        info.addPara(typeStr, Misc.getGrayColor(), 0f);
1437                                                } else {
1438                                                        info.addPara("Special", Misc.getGrayColor(), 0f);
1439                                                }
1440                                        }
1441                                        @Override
1442                                        public boolean matchesTags(Set<String> tags) {
1443                                                if (tags.contains(ALL_TYPES)) return true;
1444                                                Description desc = Global.getSettings().getDescription(spec.getId(), Type.SHIP_SYSTEM);
1445                                                String typeStr = desc.getText2();
1446                                                if (!desc.hasText2()) typeStr = "Special";
1447                                                return tags.contains(typeStr);
1448                                        }
1449                                        
1450                                        @Override
1451                                        public boolean isVignetteIcon() {
1452                                                return true;
1453                                        }
1454                                        
1455                                        @Override
1456                                        public Color getIconColor() {
1457                                                return Misc.getBasePlayerColor();
1458                                        }
1459
1460                                        @Override
1461                                        public Set<String> getUnlockRelatedTags() {
1462                                                LinkedHashSet<String> tags = new LinkedHashSet<>(spec.getTags());
1463                                                tags.add(Tags.CODEX_REQUIRE_RELATED);
1464                                                return tags;
1465                                        }
1466                                        
1467                                        @Override
1468                                        public boolean isUnlockedIfRequiresUnlock() {
1469                                                return SharedUnlockData.get().isPlayerAwareOfShipSystem(spec.getId());
1470                                        }
1471                                        
1472                                        @Override
1473                                        public String getSearchString() {
1474                                                return super.getSearchString();// + " " + typeStr;
1475                                        }
1476                        };
1477                        
1478                        parent.addChild(curr);
1479                }
1480        }
1481        
1482        
1483        public static void populateSkills(CodexEntryPlugin parent) {
1484                List<String> skillIds = Global.getSettings().getSkillIds();
1485                for (String skillId : skillIds) {
1486                        SkillSpecAPI spec = Global.getSettings().getSkillSpec(skillId);
1487                        String aptitude = getAptitudeName(spec);
1488                        String aptStr = "";
1489                        if (aptitude != null && !aptitude.isBlank()) {
1490                                aptStr = " - " + aptitude + " skill";
1491                        }
1492
1493                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1494                        if (spec.isAptitudeEffect()) continue;
1495                        
1496                        if (!spec.hasTag(Tags.SHOW_IN_CODEX) && !spec.hasTag(Tags.CODEX_UNLOCKABLE)) {
1497                                if (spec.hasTag(Skills.TAG_NPC_ONLY)) continue;
1498                                if (spec.hasTag(Skills.TAG_AI_CORE_ONLY)) continue;
1499                        };
1500                        if (spec.hasTag(Skills.TAG_DEPRECATED)) continue;
1501                        
1502                        CodexEntryV2 curr = new CodexEntryV2(getSkillEntryId(spec.getId()), 
1503                                spec.getName() + aptStr, spec.getSpriteName(), spec) {
1504                                        @Override
1505                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1506                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
1507                                                if (mode == ListMode.RELATED_ENTRIES || true) {
1508                                                        String aptitude = getAptitudeName(spec);
1509                                                        if (aptitude != null && !aptitude.isBlank()) {
1510                                                                info.addPara(aptitude + " skill", Misc.getGrayColor(), 0f);
1511                                                        } else {
1512                                                                info.addPara("Skill", Misc.getGrayColor(), 0f);
1513                                                        }
1514                                                }
1515                                        }
1516                                        
1517                                        @Override
1518                                        public boolean matchesTags(Set<String> tags) {
1519                                                if (spec.isElite() && !tags.contains(PILOTED_SHIP)) return false;
1520                                                if (!spec.isElite() && !tags.contains(OTHER)) return false;
1521                                                
1522                                                String aptitude = getAptitudeName(spec);
1523                                                if ((aptitude == null || aptitude.isBlank()) && !tags.contains(ALL_APTITUDES)) {
1524                                                        return false;
1525                                                }
1526                                                return tags.contains(aptitude);
1527                                        }
1528                                        
1529                                        @Override
1530                                        public boolean isVignetteIcon() {
1531                                                return true;
1532                                        }
1533                                        
1534                                        @Override
1535                                        public Color getIconColor() {
1536                                                return Color.white;
1537                                        }
1538
1539                                        @Override
1540                                        public Set<String> getUnlockRelatedTags() {
1541                                                return spec.getTags();
1542                                        }
1543                                        
1544                                        @Override
1545                                        public boolean isUnlockedIfRequiresUnlock() {
1546                                                return SharedUnlockData.get().isPlayerAwareOfSkill(spec.getId());
1547                                        }
1548                                        
1549                                        @Override
1550                                        public String getSearchString() {
1551                                                String aptitude = getAptitudeName(spec);
1552                                                return super.getSearchString() + " " + aptitude;
1553                                        }
1554                        };
1555                        
1556                        parent.addChild(curr);
1557                }
1558        }
1559        
1560        public static void populateAbilities(CodexEntryPlugin parent) {
1561                List<String> ids = Global.getSettings().getSortedAbilityIds();
1562                for (String id : ids) {
1563                        AbilitySpecAPI spec = Global.getSettings().getAbilitySpec(id);
1564
1565                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1566                        
1567                        CodexEntryV2 curr = new CodexEntryV2(getAbilityEntryId(spec.getId()), 
1568                                spec.getName(), spec.getIconName(), spec) {
1569                                        @Override
1570                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1571                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
1572                                                if (mode == ListMode.RELATED_ENTRIES) {
1573                                                        info.addPara("Ability", Misc.getGrayColor(), 0f);
1574                                                }
1575                                        }
1576                                        @Override
1577                                        public boolean matchesTags(Set<String> tags) {
1578                                                return super.matchesTags(tags);
1579                                        }
1580                                        
1581                                        @Override
1582                                        public boolean isVignetteIcon() {
1583                                                return true;
1584                                        }
1585                                        
1586                                        @Override
1587                                        public Color getIconColor() {
1588                                                return Color.white;
1589                                        }
1590
1591                                        @Override
1592                                        public Set<String> getUnlockRelatedTags() {
1593                                                return spec.getTags();
1594                                        }
1595                                        
1596                                        @Override
1597                                        public boolean isUnlockedIfRequiresUnlock() {
1598                                                return SharedUnlockData.get().isPlayerAwareOfAbility(spec.getId());
1599                                        }
1600                                        
1601                                        @Override
1602                                        public String getSearchString() {
1603                                                return super.getSearchString();
1604                                        }
1605                        };
1606                        
1607                        parent.addChild(curr);
1608                }
1609        }
1610        
1611        public static String getAptitudeName(SkillSpecAPI spec) {
1612                String aptitude = spec.getGoverningAptitudeName();
1613                if (spec.hasTag(Skills.TAG_AI_CORE_ONLY)) {
1614                        aptitude = "AI core";
1615                }
1616                return aptitude;
1617        }
1618        
1619        
1620        public static void sortSkillsCategory() {
1621                CodexEntryPlugin skills = getEntry(CAT_SKILLS);
1622                Collections.sort(skills.getChildren(), new Comparator<CodexEntryPlugin>() {
1623                        @Override
1624                        public int compare(CodexEntryPlugin o1, CodexEntryPlugin o2) {
1625                                SkillSpecAPI s1 = null;
1626                                SkillSpecAPI s2 = null;
1627                                if (o1.getParam() instanceof SkillSpecAPI) {
1628                                        s1 = (SkillSpecAPI) o1.getParam();
1629                                }
1630                                if (o2.getParam() instanceof SkillSpecAPI) {
1631                                        s2 = (SkillSpecAPI) o2.getParam();
1632                                }
1633                                if (s1 != null && s2 == null) return Integer.MIN_VALUE;
1634                                if (s2 != null && s1 == null) return Integer.MAX_VALUE;
1635                                if (s1 == null && s2 == null) return o1.getTitle().compareTo(o2.getTitle());
1636                                
1637                                int tier1 = 0;
1638                                int tier2 = 0;
1639                                if (s1.hasTag(Skills.TAG_AI_CORE_ONLY)) tier1 = 5;
1640                                if (s2.hasTag(Skills.TAG_AI_CORE_ONLY)) tier2 = 5;
1641                                
1642                                if (tier1 != tier2) return tier1 - tier2;
1643                                
1644                                int diff = s1.getGoverningAptitudeOrder() - s2.getGoverningAptitudeOrder();
1645                                if (diff != 0) return diff;
1646                                
1647                                return (int) Math.signum(s1.getOrder() - s2.getOrder());
1648                        }
1649                });
1650        }
1651        
1652        
1653        public static void populateWeapons(CodexEntryPlugin parent) {
1654                
1655                for (WeaponSpecAPI spec : Global.getSettings().getActuallyAllWeaponSpecs()) {
1656                        //if (spec.getTags().contains(Tags.RESTRICTED)) continue;
1657                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1658                        if ((spec.getAIHints().contains(AIHints.SYSTEM) || spec.getType() == WeaponType.SYSTEM) &&
1659                                        !(spec.getAIHints().contains(AIHints.SHOW_IN_CODEX) ||
1660                                                        spec.hasTag(Tags.SHOW_IN_CODEX))) {
1661                                continue;
1662                        }
1663                        
1664                        CodexEntryV2 curr = new CodexEntryV2(getWeaponEntryId(spec.getWeaponId()), 
1665                                spec.getWeaponName(), null, spec) {
1666                                        @Override
1667                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1668                                                info.addPara(spec.getWeaponName(), Misc.getBasePlayerColor(), 0f);
1669                                                if (mode == ListMode.RELATED_ENTRIES || true) {
1670                                                        String size = spec.getSize().getDisplayName();
1671                                                        WeaponType type = spec.getMountType();
1672                                                        if (type == null) type = spec.getType();
1673                                                        
1674                                                        info.addPara(size + " " + type.getDisplayName().toLowerCase() + " weapon", Misc.getGrayColor(), 0f);
1675                                                        //info.addPara("Weapon", Misc.getGrayColor(), 0f);
1676                                                }
1677                                        }
1678
1679                                        @Override
1680                                        public boolean matchesTags(Set<String> tags) {
1681                                                WeaponSize size = spec.getSize();
1682                                                
1683                                                String sizeTag = null;
1684                                                if (size == WeaponSize.SMALL) sizeTag = SMALL;
1685                                                if (size == WeaponSize.MEDIUM) sizeTag = MEDIUM;
1686                                                if (size == WeaponSize.LARGE) sizeTag = LARGE;
1687                                                if (spec.getAIHints().contains(AIHints.SYSTEM) && 
1688                                                                spec.getPrimaryRoleStr() != null &&
1689                                                                spec.getPrimaryRoleStr() != null && 
1690                                                                spec.getPrimaryRoleStr().endsWith("(Fighter)")) {
1691                                                        sizeTag = FIGHTER_WEAPON;
1692                                                }
1693                                                
1694                                                if (sizeTag != null && !tags.contains(sizeTag)) return false;
1695                                                
1696                                                String damTypeTag = null;
1697                                                DamageType type = spec.getDamageType();
1698                                                if (type == DamageType.HIGH_EXPLOSIVE) damTypeTag = HIGH_EXPLOSIVE;
1699                                                if (type == DamageType.KINETIC) damTypeTag = KINETIC;
1700                                                if (type == DamageType.ENERGY) damTypeTag = DAM_ENERGY;
1701                                                if (type == DamageType.FRAGMENTATION) damTypeTag = FRAGMENTATION;
1702                                                if (type == DamageType.OTHER) damTypeTag = OTHER;
1703                                                if (damTypeTag != null && !tags.contains(damTypeTag)) return false;
1704                                                
1705                                                String m = spec.getManufacturer();
1706                                                if (m != null && !tags.contains(m) && !tags.contains(ALL_TECHS)) return false;
1707                                                
1708                                                
1709                                                if (spec.getMountType() == WeaponType.HYBRID && tags.contains(HYBRID)) return true;
1710                                                if (spec.getMountType() == WeaponType.COMPOSITE && tags.contains(COMPOSITE)) return true;
1711                                                if (spec.getMountType() == WeaponType.SYNERGY && tags.contains(SYNERGY)) return true;
1712                                                if (spec.getMountType() == WeaponType.UNIVERSAL && tags.contains(UNIVERSAL)) return true;
1713                                                if (spec.isBeam() && tags.contains(BEAM)) return true;
1714                                                
1715                                                if (spec.getType() == WeaponType.BALLISTIC && !tags.contains(BALLISTIC)) return false;
1716                                                if (spec.getType() == WeaponType.MISSILE && !tags.contains(MISSILE)) return false;
1717                                                if (spec.getType() == WeaponType.ENERGY && !tags.contains(ENERGY)) return false;
1718                                                
1719                                                return true;
1720                                        }
1721                                        
1722                                        @Override
1723                                        public Set<String> getUnlockRelatedTags() {
1724                                                return spec.getTags();
1725                                        }
1726                                        
1727                                        @Override
1728                                        public boolean isUnlockedIfRequiresUnlock() {
1729                                                return SharedUnlockData.get().isPlayerAwareOfWeapon(spec.getWeaponId());
1730                                        }
1731                                        
1732                        };
1733                        parent.addChild(curr);
1734                }
1735        }
1736        
1737        public static void populateFighters(CodexEntryPlugin parent) {
1738                
1739                for (final FighterWingSpecAPI spec : Global.getSettings().getAllFighterWingSpecs()) {
1740                        if (spec.getVariant().getHints().contains(ShipTypeHints.HIDE_IN_CODEX)) continue;
1741                        if (spec.getTags().contains(Tags.HIDE_IN_CODEX)) continue;                      
1742                        //if (spec.getTags().contains(Tags.RESTRICTED)) continue;                       
1743                        
1744                        ShipVariantAPI variant = spec.getVariant();
1745                        String nameStr = variant.getHullSpec().getHullName() + " " + variant.getDisplayName();
1746                        if (spec.hasTag(Tags.SWARM_FIGHTER)) {
1747                                nameStr = variant.getDisplayName() + " " + variant.getHullSpec().getHullName();
1748                        }
1749                        if (variant.getDisplayName().isEmpty()) {
1750                                nameStr = variant.getHullSpec().getHullName();
1751                        }
1752                        String nameStr2 = nameStr;
1753                        CodexEntryV2 curr = new CodexEntryV2(getFighterEntryId(spec.getId()), 
1754                                nameStr2, null, spec) {
1755                                        @Override
1756                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1757                                                info.addPara(nameStr2, Misc.getBasePlayerColor(), 0f);
1758                                                if (mode == ListMode.RELATED_ENTRIES || true) {
1759                                                        String role = spec.getRole().name().toLowerCase();
1760                                                        role = Misc.ucFirst(role);
1761                                                        if (spec.getRoleDesc() != null && !spec.getRoleDesc().isEmpty()) {
1762                                                                role = spec.getRoleDesc();
1763                                                        }
1764                                                        info.addPara(role, Misc.getGrayColor(), 0f);
1765                                                }
1766                                        }
1767
1768                                        @Override
1769                                        public boolean matchesTags(Set<String> tags) {
1770                                                if (spec.getRole() == WingRole.FIGHTER) {
1771                                                        if (!tags.contains(FIGHTER)) return false;
1772                                                } else if (spec.getRole() == WingRole.BOMBER) {
1773                                                        if (!tags.contains(BOMBER)) return false;
1774                                                } else if (spec.getRole() == WingRole.INTERCEPTOR) {
1775                                                        if (!tags.contains(INTERCEPTOR)) return false;
1776                                                } else {
1777                                                        if (!tags.contains(OTHER)) return false;
1778                                                }
1779                                                
1780                                                String m = spec.getVariant().getHullSpec().getManufacturer();
1781                                                if (m != null && !tags.contains(m) && !tags.contains(ALL_TECHS)) return false;
1782                                                
1783                                                return true;
1784                                        }
1785                                        
1786                                        @Override
1787                                        public Set<String> getUnlockRelatedTags() {
1788                                                return spec.getTags();
1789                                        }
1790                                        
1791                                        @Override
1792                                        public boolean isUnlockedIfRequiresUnlock() {
1793                                                return SharedUnlockData.get().isPlayerAwareOfFighter(spec.getId());
1794                                        }
1795                                        
1796                        };
1797                        parent.addChild(curr);
1798                }
1799        }
1800        
1801        public static class GalleryEntryData implements WithSourceMod {
1802                public String sprite;
1803                public Description desc;
1804                public ModSpecAPI sourceMod;
1805                @Override
1806                public ModSpecAPI getSourceMod() {
1807                        return sourceMod;
1808                }
1809        }
1810        public static void populateGallery(CodexEntryPlugin parent) {
1811                String cat = "illustrations";
1812                for (String key : Global.getSettings().getSpriteKeys(cat)) {
1813                        String sprite = Global.getSettings().getSpriteName(cat, key);
1814                        
1815                        Description desc = Global.getSettings().getDescription(key, Type.GALLERY);
1816                        
1817                        if (desc.getText3().toLowerCase().contains(Tags.HIDE_IN_CODEX)) continue;
1818                        
1819                        if (!desc.hasText1()) continue;
1820                        
1821                        if (USE_KEY_NAMES_FOR_GALLERY) {
1822                                String text = key.replaceAll("_", " ");
1823                                text = Misc.ucFirst(text);
1824                                desc.setText1(text);
1825                                desc.setText2(text);
1826                        }
1827                        
1828                        GalleryEntryData data = new GalleryEntryData();
1829                        data.sprite = sprite;
1830                        data.desc = desc;
1831                        data.sourceMod = desc.getSourceMod();
1832                        
1833                        CodexEntryV2 curr = new CodexEntryV2(getGalleryEntryId(key), 
1834                                        desc.getText1(), sprite, data) {
1835                                @Override
1836                                public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1837                                        info.addPara(desc.getText1(), Misc.getBasePlayerColor(), 0f);
1838                                        if (mode == ListMode.RELATED_ENTRIES) {
1839                                                info.addPara("Illustration", Misc.getGrayColor(), 0f);
1840                                        }
1841                                }
1842                                @Override
1843                                public boolean isVignetteIcon() {
1844                                        return true;
1845                                }
1846                                @Override
1847                                public boolean isVisible() {
1848                                        return super.isVisible();
1849                                }
1850                                @Override
1851                                public boolean isLocked() {
1852                                        if (codexFullyUnlocked()) return false;
1853                                        if (SharedUnlockData.get().isPlayerAwareOfIllustration(key)) {
1854                                                return false;
1855                                        }
1856                                        return !desc.getText3().toLowerCase().contains("unlocked");
1857                                }
1858                        };
1859                        parent.addChild(curr);
1860                }
1861        }
1862        
1863        public static void populateSpecialItems(CodexEntryPlugin parent) {
1864                List<SpecialItemSpecAPI> specs = Global.getSettings().getAllSpecialItemSpecs();
1865                for (final SpecialItemSpecAPI spec : specs) {
1866                        //if (spec.hasTag(Items.TAG_BLUEPRINT_PACKAGE)) continue;
1867                        if (spec.hasTag(Items.TAG_SINGLE_BP)) continue;
1868                        if (spec.hasTag(Items.TAG_MODSPEC)) continue;
1869                        if (spec.getId().equals(Items.INDUSTRY_BP)) continue;
1870                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1871                        
1872                        CodexEntryV2 curr = new CodexEntryV2(getItemEntryId(spec.getId()), 
1873                                spec.getName(), spec.getIconName(), spec) {
1874                                        @Override
1875                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1876                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
1877                                                if (mode == ListMode.RELATED_ENTRIES) {
1878                                                        info.addPara("Special item", Misc.getGrayColor(), 0f);
1879                                                }
1880                                        }
1881
1882                                        @Override
1883                                        public boolean matchesTags(Set<String> tags) {
1884                                                boolean colony = spec.hasTag(Items.TAG_COLONY_ITEM);
1885                                                boolean bp = spec.hasTag(Items.TAG_BLUEPRINT_PACKAGE);
1886                                                if (tags.contains(OTHER) && !colony && !bp) return true;
1887                                                if (tags.contains(COLONY) && colony) return true;
1888                                                if (tags.contains(BLUEPRINTS) && bp) return true;
1889                                                return false;
1890                                        }
1891                                        
1892                                        @Override
1893                                        public Set<String> getUnlockRelatedTags() {
1894                                                return spec.getTags();
1895                                        }
1896                                        
1897                                        @Override
1898                                        public boolean isUnlockedIfRequiresUnlock() {
1899                                                return SharedUnlockData.get().isPlayerAwareOfSpecialItem(spec.getId());
1900                                        }
1901
1902                        };
1903                        parent.addChild(curr);
1904                }
1905        }
1906        
1907        public static void populateCommodities(CodexEntryPlugin commodities, CodexEntryPlugin items) {
1908                List<CommoditySpecAPI> specs = Global.getSettings().getAllCommoditySpecs();
1909                for (final CommoditySpecAPI spec : specs) {
1910                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
1911
1912                        boolean special = spec.hasTag(Commodities.TAG_AI_CORE) || spec.hasTag(Commodities.TAG_NON_ECONOMIC);
1913                        CodexEntryV2 curr = new CodexEntryV2(getCommodityEntryId(spec.getId()), 
1914                                spec.getName(), spec.getIconName(), spec) {
1915                                        @Override
1916                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1917                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
1918                                                if (mode == ListMode.RELATED_ENTRIES) {
1919                                                        if (special) {
1920                                                                info.addPara("Special item", Misc.getGrayColor(), 0f);
1921                                                        } else {
1922                                                                info.addPara("Commodity", Misc.getGrayColor(), 0f);
1923                                                        }
1924                                                }
1925                                                
1926                                        }
1927                                        @Override
1928                                        public boolean matchesTags(Set<String> tags) {
1929                                                if (special) {
1930                                                        boolean aiCore = spec.hasTag(Commodities.TAG_AI_CORE); 
1931                                                        if (tags.contains(OTHER) && !aiCore) return true;
1932                                                        if (tags.contains(AI_CORE) && aiCore) return true;
1933                                                        return false;
1934                                                } else {
1935                                                        return super.matchesTags(tags);
1936                                                }
1937                                        }
1938                                        
1939                                        @Override
1940                                        public Set<String> getUnlockRelatedTags() {
1941                                                return spec.getTags();
1942                                        }
1943                                        
1944                                        @Override
1945                                        public boolean isUnlockedIfRequiresUnlock() {
1946                                                return SharedUnlockData.get().isPlayerAwareOfCommodity(spec.getId());
1947                                        }
1948
1949                        };
1950                        if (special) {
1951                                items.addChild(curr);
1952                        } else {
1953                                commodities.addChild(curr);
1954                        }
1955                }
1956        }
1957
1958//      public static CodexEntryV2 createFactionsCategory() {
1959//              CodexEntryV2 cat = new CodexEntryV2(CAT_FACTIONS, "Factions", getIcon(CAT_FACTIONS)) {
1960//
1961//              };
1962//              return cat;
1963//      }
1964//      public static void populateFactions(CodexEntryPlugin factions) {
1965//              List<FactionSpecAPI> specs = Global.getSettings().getAllFactionSpecs();
1966//              for (final FactionSpecAPI spec : specs) {
1967//                      if (!spec.isShowInIntelTab()) continue;
1968//
1969//                      String name = Misc.ucFirst(spec.getDisplayName());
1970//                      CodexEntryV2 curr = new CodexEntryV2(getFactionEntryId(spec.getId()), 
1971//                                      name, spec.getCrest(), spec) {
1972//                                      @Override
1973//                                      public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
1974//                                              info.addPara(name, Misc.getBasePlayerColor(), 0f);
1975//                                              if (mode == ListMode.RELATED_ENTRIES) {
1976//                                                      info.addPara("Faction", Misc.getGrayColor(), 0f);
1977//                                              }
1978//                                              
1979//                                      }
1980//                                      @Override
1981//                                      public boolean isVignetteIcon() {
1982//                                              return true;
1983//                                      }
1984//                                      @Override
1985//                                      public Set<String> getUnlockRelatedTags() {
1986//                                              return super.getUnlockRelatedTags();
1987//                                      }
1988//                                      
1989//                                      @Override
1990//                                      public boolean isUnlockedIfRequiresUnlock() {
1991//                                              return super.isUnlockedIfRequiresUnlock();
1992//                                              //return SharedUnlockData.get().isPlayerAwareOfCommodity(spec.getId());
1993//                                      }
1994//                      };
1995//                      factions.addChild(curr);
1996//              }
1997//      }               
1998
1999// doesn't work for various reasons - need icons, and terrain REALLY depends on being inside a campaign
2000// would need to create a parallel set of codex entries, not use the terrain plugins directly, which has its own problems 
2001//      public static void populateTerrain(CodexEntryPlugin terrain) {
2002//              List<TerrainSpecAPI> specs = Global.getSettings().getAllTerrainSpecs();
2003//              for (final TerrainSpecAPI spec : specs) {
2004//                      if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
2005//
2006//                      SectorEntityToken temp = Global.getSettings().createLocationToken(0, 0);
2007//                      CampaignTerrainPlugin plugin = spec.getNewPluginInstance(temp, null);
2008//                      String name = Misc.ucFirst(plugin.getNameForTooltip());
2009//                      CodexEntryV2 curr = new CodexEntryV2(getFactionEntryId(spec.getId()), 
2010//                                      name, null, spec) {
2011//                              @Override
2012//                              public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
2013//                                      info.addPara(name, Misc.getBasePlayerColor(), 0f);
2014//                                      if (mode == ListMode.RELATED_ENTRIES) {
2015//                                              info.addPara("Terrain", Misc.getGrayColor(), 0f);
2016//                                      }
2017//
2018//                              }
2019//                              @Override
2020//                              public boolean isVignetteIcon() {
2021//                                      return true;
2022//                              }
2023//                              @Override
2024//                              public Set<String> getUnlockRelatedTags() {
2025//                                      return super.getUnlockRelatedTags();
2026//                              }
2027//
2028//                              @Override
2029//                              public boolean isUnlockedIfRequiresUnlock() {
2030//                                      return super.isUnlockedIfRequiresUnlock();
2031//                                      //return SharedUnlockData.get().isPlayerAwareOfCommodity(spec.getId());
2032//                              }
2033//                      };
2034//                      terrain.addChild(curr);
2035//              }
2036//      }               
2037        
2038        public static void populateHullMods(CodexEntryPlugin parent) {
2039                List<HullModSpecAPI> specs = Global.getSettings().getAllHullModSpecs();
2040                for (final HullModSpecAPI spec : specs) {
2041                        if (spec.isHiddenEverywhere()) continue;
2042                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
2043
2044                        CodexEntryV2 curr = new CodexEntryV2(getHullmodEntryId(spec.getId()), 
2045                                spec.getDisplayName(), spec.getSpriteName(), spec) {
2046                                        @Override
2047                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
2048                                                info.addPara(spec.getDisplayName(), Misc.getBasePlayerColor(), 0f);
2049                                                if (mode == ListMode.RELATED_ENTRIES) {
2050                                                        info.addPara("Hullmod", Misc.getGrayColor(), 0f);
2051                                                }
2052                                        }
2053                                        @Override
2054                                        public boolean isVignetteIcon() {
2055                                                return true;
2056                                        }
2057                                        
2058                                        @Override
2059                                        public boolean matchesTags(Set<String> tags) {
2060                                                if (!tags.contains(spec.getManufacturer())) {
2061                                                        return false;
2062                                                }
2063                                                
2064                                                if (tags.contains(ALL_TYPES)) return true;
2065                                                
2066                                                boolean hasATag = false;
2067                                                for (String tag : spec.getUITags()) {
2068                                                        if (tags.contains(tag)) hasATag = true;
2069                                                }
2070                                                if (spec.hasTag(Tags.HULLMOD_DMOD) && tags.contains(DMODS)) {
2071                                                        hasATag = true;
2072                                                }
2073                                                if (spec.isHidden() && !spec.hasTag(Tags.HULLMOD_DMOD) && tags.contains(INTRINSIC)) {
2074                                                        hasATag = true;
2075                                                }
2076                                                if (!hasATag) return false;
2077                                                
2078                                                return true;
2079                                        }
2080                                        
2081                                        @Override
2082                                        public Set<String> getUnlockRelatedTags() {
2083                                                return spec.getTags();
2084                                        }
2085                                        
2086                                        @Override
2087                                        public boolean isUnlockedIfRequiresUnlock() {
2088                                                return SharedUnlockData.get().isPlayerAwareOfHullmod(spec.getId());
2089                                        }
2090                        };
2091                        
2092                        parent.addChild(curr);
2093                }
2094        }
2095        
2096        public static void populateIndustries(CodexEntryPlugin parent) {
2097                List<IndustrySpecAPI> specs = Global.getSettings().getAllIndustrySpecs();
2098                for (final IndustrySpecAPI spec : specs) {
2099                        if (spec.hasTag(Industries.TAG_PARENT)) continue;
2100                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
2101                        
2102                        CodexEntryV2 curr = new CodexEntryV2(getIndustryEntryId(spec.getId()), 
2103                                        spec.getName(), spec.getImageName(), spec) {
2104                                @Override
2105                                public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
2106                                        info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
2107                                        if (mode == ListMode.RELATED_ENTRIES || true) {
2108                                                //info.addPara("Industry", Misc.getGrayColor(), 0f);
2109                                                boolean structure = spec.hasTag(Industries.TAG_STRUCTURE);
2110                                                String type = "Industry";
2111                                                if (structure) type = "Structure";
2112                                                info.addPara(type, Misc.getGrayColor(), 0f);
2113                                        }
2114                                }
2115                                @Override
2116                                public boolean isVignetteIcon() {
2117                                        return true;
2118                                }
2119                                @Override
2120                                public boolean matchesTags(Set<String> tags) {
2121                                        boolean industry = spec.hasTag(Industries.TAG_INDUSTRY);
2122                                        boolean structure = spec.hasTag(Industries.TAG_STRUCTURE);
2123                                        boolean station = spec.hasTag(Industries.TAG_STATION);
2124                                        if (tags.contains(OTHER) && !industry && !structure && !station) return true;
2125                                        if (tags.contains(INDUSTRIES) && industry) return true;
2126                                        if (tags.contains(STRUCTURES) && structure && !station) return true;
2127                                        if (tags.contains(STATIONS) && station) return true;
2128                                        return false;
2129                                }
2130                                @Override
2131                                public Set<String> getUnlockRelatedTags() {
2132                                        return spec.getTags();
2133                                }
2134                                @Override
2135                                public boolean isUnlockedIfRequiresUnlock() {
2136                                        return SharedUnlockData.get().isPlayerAwareOfIndustry(spec.getId());
2137                                }
2138                        };
2139                        parent.addChild(curr);
2140                }
2141        }
2142        
2143        public static void populateStarsAndPlanets(CodexEntryPlugin parent) {
2144                List<PlanetSpecAPI> specs = Global.getSettings().getAllPlanetSpecs();
2145                for (final PlanetSpecAPI spec : specs) {
2146                        //if (spec.isNebulaCenter()) continue;
2147                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
2148
2149                        CodexEntryV2 curr = new CodexEntryV2(getPlanetEntryId(spec.getPlanetType()), 
2150                                spec.getName(), spec.getIconTexture(), spec) {
2151                                        @Override
2152                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
2153                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
2154                                                String type = "Planet";
2155                                                if (spec.isGasGiant()) type = "Gas giant";
2156                                                else if (spec.isNebulaCenter()) type = "Nebula";
2157                                                else if (spec.isStar()) type = "Star";
2158                                                if (mode == ListMode.RELATED_ENTRIES) {
2159                                                        info.addPara(type, Misc.getGrayColor(), 0f);
2160                                                }
2161                                        }
2162                                        @Override
2163                                        public boolean matchesTags(Set<String> tags) {
2164                                                if (tags.contains(ALL_TYPES)) return true;
2165                                                
2166                                                if (tags.contains(HABITABLE) && Misc.canPlanetTypeRollHabitable(spec)) {
2167                                                        return true;
2168                                                }
2169                                                if (spec.isGasGiant() && !tags.contains(GAS_GIANTS)) {
2170                                                        return false;
2171                                                } else if (spec.isStar() && !tags.contains(STARS)) {
2172                                                        return false;
2173                                                } else if (!spec.isGasGiant() && !spec.isStar() && !tags.contains(PLANETS)) {
2174                                                        return false;
2175                                                }
2176                                                
2177                                                return true;
2178                                        }
2179                                        @Override
2180                                        public Set<String> getUnlockRelatedTags() {
2181                                                return spec.getTags();
2182                                        }
2183                                        @Override
2184                                        public boolean isUnlockedIfRequiresUnlock() {
2185                                                return SharedUnlockData.get().isPlayerAwareOfPlanet(spec.getPlanetType());
2186                                        }
2187                        };
2188                        
2189                        parent.addChild(curr);
2190                }
2191        }
2192        
2193        
2194        public static void populatePlanetaryConditions(CodexEntryPlugin parent) {
2195                List<MarketConditionSpecAPI> specs = Global.getSettings().getAllMarketConditionSpecs();
2196                for (final MarketConditionSpecAPI spec : specs) {
2197
2198                        if (spec.hasTag(Tags.HIDE_IN_CODEX)) continue;
2199                        
2200                        if (spec.getGenSpec() == null &&
2201                                        !spec.hasTag(Tags.SHOW_IN_PLANET_LIST) &&
2202                                        !spec.hasTag(Tags.SHOW_IN_CODEX)) {
2203                                continue;
2204                        }
2205                        
2206                        CodexEntryV2 curr = new CodexEntryV2(getConditionEntryId(spec.getId()), 
2207                                spec.getName(), spec.getIcon(), spec) {
2208                                        @Override
2209                                        public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
2210                                                info.addPara(spec.getName(), Misc.getBasePlayerColor(), 0f);
2211                                                if (mode == ListMode.RELATED_ENTRIES) {
2212                                                        info.addPara("Planetary condition", Misc.getGrayColor(), 0f);
2213                                                }
2214                                        }
2215                                        @Override
2216                                        public boolean isVignetteIcon() {
2217                                                return true;
2218                                        }
2219                                        
2220                                        @Override
2221                                        public boolean matchesTags(Set<String> tags) {
2222                                                boolean res = ResourceDepositsCondition.COMMODITY.containsKey(spec.getId());
2223                                                if (tags.contains(OTHER) && !res) return true;
2224                                                if (tags.contains(RESOURCES) && res) return true;
2225                                                return false;
2226                                        }
2227                                        @Override
2228                                        public Set<String> getUnlockRelatedTags() {
2229                                                return spec.getTags();
2230                                        }
2231                                        @Override
2232                                        public boolean isUnlockedIfRequiresUnlock() {
2233                                                return SharedUnlockData.get().isPlayerAwareOfCondition(spec.getId());
2234                                        }
2235                        };
2236                        
2237                        parent.addChild(curr);
2238                }
2239        }
2240        
2241        
2242        public static void linkRelatedEntries() {
2243                CodexEntryPlugin ships = getEntry(CAT_SHIPS);
2244                CodexEntryPlugin stations = getEntry(CAT_STATIONS);
2245                CodexEntryPlugin fighters = getEntry(CAT_FIGHTERS);
2246                CodexEntryPlugin weapons = getEntry(CAT_WEAPONS);
2247                CodexEntryPlugin hullmods = getEntry(CAT_HULLMODS);
2248                CodexEntryPlugin shipSystems = getEntry(CAT_SHIP_SYSTEMS);
2249                CodexEntryPlugin planets = getEntry(CAT_STARS_AND_PLANETS);
2250                CodexEntryPlugin conditions = getEntry(CAT_PLANETARY_CONDITIONS);
2251                CodexEntryPlugin items = getEntry(CAT_SPECIAL_ITEMS);
2252                CodexEntryPlugin commodities = getEntry(CAT_COMMODITIES);
2253                CodexEntryPlugin industries = getEntry(CAT_INDUSTRIES);
2254                CodexEntryPlugin skills = getEntry(CAT_SKILLS);
2255                CodexEntryPlugin abilities = getEntry(CAT_ABILITIES);
2256                //CodexEntryPlugin factions = getEntry(CAT_FACTIONS);
2257                
2258                List<CodexEntryPlugin> shipsAndStations = new ArrayList<>();
2259                shipsAndStations.addAll(ships.getChildren());
2260                shipsAndStations.addAll(stations.getChildren());
2261                
2262                // link all ships that are based on the same hull - skins etc
2263                ListMap<CodexEntryPlugin> relatedHulls = new ListMap<>();
2264                for (CodexEntryPlugin ship : shipsAndStations) {
2265                        if (ship.getParam() instanceof ShipHullSpecAPI) {
2266                                ShipHullSpecAPI spec = (ShipHullSpecAPI) ship.getParam();
2267                                String baseId = getBaseHullIdEvenIfNotRestorableTo(spec);
2268                                relatedHulls.add(baseId, ship);
2269                                if (spec.hasTag(Tags.DWELLER)) {
2270                                        relatedHulls.add(Tags.DWELLER + "_allParts", ship);
2271                                }
2272                        }
2273                }
2274                for (List<CodexEntryPlugin> list : relatedHulls.values()) {
2275                        makeRelated(list);
2276                }
2277                
2278                for (CodexEntryPlugin ship : shipsAndStations) {
2279                        if (ship.getParam() instanceof ShipHullSpecAPI) {
2280                                ShipHullSpecAPI spec = (ShipHullSpecAPI) ship.getParam();
2281                                
2282                                // link ships and their systems
2283                                String sysId = spec.getShipSystemId();
2284                                if (sysId != null && !sysId.isBlank()) {
2285                                        String key = getShipSystemEntryId(sysId);
2286                                        CodexEntryPlugin sys = getEntry(key);
2287                                        if (sys != null) {
2288                                                sys.addRelatedEntry(ship);
2289                                                ship.addRelatedEntry(sys);
2290                                        
2291                                                // link ship systems and ships with their drones, if any
2292                                                if (sys.getParam() instanceof ShipSystemSpecAPI) {
2293                                                        ShipSystemSpecAPI sysSpec = (ShipSystemSpecAPI) sys.getParam();
2294                                                        if (sysSpec.getDroneVariant() != null) {
2295                                                                ShipVariantAPI drone = Global.getSettings().getVariant(sysSpec.getDroneVariant());
2296                                                                if (drone != null) {
2297                                                                        String droneHullId = getBaseHullId(drone.getHullSpec());
2298                                                                        String droneEntryId = getShipEntryId(droneHullId);
2299                                                                        makeRelated(ship.getId(), droneEntryId);
2300                                                                        makeRelated(sys.getId(), droneEntryId);
2301                                                                }
2302                                                        }
2303                                                }
2304                                        }
2305                                }
2306                                
2307                                if (!spec.isPhase()) {
2308                                        sysId = spec.getShipDefenseId();
2309                                        String key = getShipSystemEntryId(sysId);
2310                                        CodexEntryPlugin sys = getEntry(key);
2311                                        if (sys != null) {
2312                                                sys.addRelatedEntry(ship);
2313                                                ship.addRelatedEntry(sys);
2314                                        }
2315                                }
2316                                
2317                                String variantId = spec.getHullId() + "_Hull";
2318                                if (spec.getCodexVariantId() != null && !spec.getCodexVariantId().isBlank()) {
2319                                        variantId = spec.getCodexVariantId();
2320                                }
2321                                ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
2322                                
2323                                // link ships and their hullmods
2324                                //for (String id : spec.getBuiltInMods()) {
2325                                for (String id : variant.getHullMods()) {
2326                                        String key = getHullmodEntryId(id);
2327                                        CodexEntryPlugin mod = getEntry(key);
2328                                        if (mod != null) {
2329                                                mod.addRelatedEntry(ship);
2330                                                ship.addRelatedEntry(mod);
2331                                                
2332                                                if (id.equals(HullMods.VAST_HANGAR)) {
2333                                                        ship.addRelatedEntry(getEntry(getHullmodEntryId(HullMods.CONVERTED_HANGAR)));
2334                                                }
2335                                        }
2336                                }
2337                                
2338                                // link ships and their built-in weapons
2339                                //for (String slotId : spec.getBuiltInWeapons().keySet()) {
2340                                for (String slotId : variant.getFittedWeaponSlots()) {
2341                                        WeaponSlotAPI slot = spec.getWeaponSlot(slotId);
2342                                        if (slot == null || slot.isDecorative() || slot.isSystemSlot()) {
2343                                                continue;
2344                                        }
2345                                        //String id = spec.getBuiltInWeapons().get(slotId);
2346                                        String id = variant.getWeaponId(slotId);
2347                                        
2348                                        String key = getWeaponEntryId(id);
2349                                        CodexEntryPlugin weapon = getEntry(key);
2350                                        if (weapon != null) {
2351                                                weapon.addRelatedEntry(ship);
2352                                                ship.addRelatedEntry(weapon);
2353                                        }
2354                                }
2355
2356                                // link ships and their built-in fighters
2357                                //for (String id : spec.getBuiltInWings()) {
2358                                for (String id : variant.getWings()) {
2359                                        String key = getFighterEntryId(id);
2360                                        CodexEntryPlugin fighter = getEntry(key);
2361                                        if (fighter != null) {
2362                                                fighter.addRelatedEntry(ship);
2363                                                ship.addRelatedEntry(fighter);
2364                                        }
2365                                }
2366                        }
2367                }
2368                
2369                
2370                
2371                // link fighters to their hullmods, systems, and weapons
2372                for (CodexEntryPlugin fighter : fighters.getChildren()) {
2373                        if (fighter.getParam() instanceof FighterWingSpecAPI) {
2374                                FighterWingSpecAPI wing = (FighterWingSpecAPI) fighter.getParam();
2375                                ShipVariantAPI variant = wing.getVariant();
2376                                ShipHullSpecAPI spec = variant.getHullSpec();
2377                                
2378                                // link fighters and their systems
2379                                String sysId = spec.getShipSystemId();
2380                                if (sysId != null) {
2381                                        String key = getShipSystemEntryId(sysId);
2382                                        CodexEntryPlugin sys = getEntry(key);
2383                                        if (sys != null) {
2384                                                sys.addRelatedEntry(fighter);
2385                                                fighter.addRelatedEntry(sys);
2386                                        }
2387                                }
2388                                
2389                                if (!spec.isPhase()) {
2390                                        sysId = spec.getShipDefenseId();
2391                                        String key = getShipSystemEntryId(sysId);
2392                                        CodexEntryPlugin sys = getEntry(key);
2393                                        if (sys != null) {
2394                                                sys.addRelatedEntry(fighter);
2395                                                fighter.addRelatedEntry(sys);
2396                                        }
2397                                }
2398                                
2399                                // link fighters and their hullmods
2400                                for (String id : variant.getHullMods()) {
2401                                        String key = getHullmodEntryId(id);
2402                                        CodexEntryPlugin mod = getEntry(key);
2403                                        if (mod != null) {
2404                                                mod.addRelatedEntry(fighter);
2405                                                fighter.addRelatedEntry(mod);
2406                                        }
2407                                }
2408                                
2409                                // link fighters and their weapons
2410                                for (String slotId : variant.getFittedWeaponSlots()) {
2411                                        String id = variant.getWeaponId(slotId);
2412                                        String key = getWeaponEntryId(id);
2413                                        CodexEntryPlugin weapon = getEntry(key);
2414                                        if (weapon != null) {
2415                                                weapon.addRelatedEntry(fighter);
2416                                                fighter.addRelatedEntry(weapon);
2417                                        }
2418                                }
2419                        }
2420                }
2421                
2422                List<CodexEntryPlugin> dem = new ArrayList<>();
2423                // link related weapons to seach other
2424                for (CodexEntryPlugin weapon : weapons.getChildren()) {
2425                        if (weapon.getParam() instanceof WeaponSpecAPI) {
2426                                WeaponSpecAPI spec = (WeaponSpecAPI) weapon.getParam();
2427                                // DEM missiles
2428                                if (spec.hasTag(Tags.DAMAGE_SOFT_FLUX) && spec.hasTag(Tags.DAMAGE_SPECIAL)) {
2429                                        dem.add(weapon);
2430                                }
2431                        }
2432                }
2433                makeRelated(dem);
2434                
2435                
2436                
2437                ListMap<CodexEntryPlugin> relatedPlanets = new ListMap<>();
2438                String gasGiantListId = "gas_giant_related_id";
2439                
2440                List<CodexEntryPlugin> habitablePlanets = new ArrayList<>();
2441                
2442                // link planets variations with the same description
2443                for (CodexEntryPlugin planet : planets.getChildren()) {
2444                        if (planet.getParam() instanceof PlanetSpecAPI) {
2445                                PlanetSpecAPI spec = (PlanetSpecAPI) planet.getParam();
2446                                String id = spec.getDescriptionId();
2447                                if (id == null) id = spec.getPlanetType();
2448                                
2449                                relatedPlanets.add(id, planet);
2450                                
2451                                if (spec.isNebulaCenter()) {
2452                                        relatedPlanets.add("nebula_related_id", planet);
2453                                }
2454                                if (spec.isGasGiant()) {
2455                                        relatedPlanets.add(gasGiantListId, planet);
2456                                }
2457                                if (spec.isPulsar()) {
2458                                        relatedPlanets.add("pulsar_related_id", planet);
2459                                }
2460                                if (spec.isBlackHole()) {
2461                                        relatedPlanets.add("black_hole_related_id", planet);
2462                                }
2463                                if (Misc.canPlanetTypeRollHabitable(spec)) {
2464                                        habitablePlanets.add(planet);
2465                                }
2466                        }
2467                }
2468                for (List<CodexEntryPlugin> list : relatedPlanets.values()) {
2469                        makeRelated(list);
2470                }
2471                makeRelated(getPlanetEntryId(Planets.PLANET_LAVA), getPlanetEntryId(Planets.PLANET_LAVA_MINOR));
2472                
2473                // link related conditions to each other
2474                ListMap<CodexEntryPlugin> relatedDeposits = new ListMap<>();
2475                for (String cid : ResourceDepositsCondition.COMMODITY.keySet()) {
2476                        String commodityId = ResourceDepositsCondition.COMMODITY.get(cid);
2477                        relatedDeposits.add(commodityId, getEntry(getConditionEntryId(cid)));
2478                }
2479                for (List<CodexEntryPlugin> list : relatedDeposits.values()) {
2480                        makeRelated(list);
2481                }
2482                
2483                makeRelated(getConditionEntryId(Conditions.RUINS_SCATTERED),
2484                                        getConditionEntryId(Conditions.RUINS_WIDESPREAD),
2485                                        getConditionEntryId(Conditions.RUINS_EXTENSIVE),
2486                                        getConditionEntryId(Conditions.RUINS_VAST));
2487                makeRelated(getConditionEntryId(Conditions.NO_ATMOSPHERE),
2488                                        getConditionEntryId(Conditions.THIN_ATMOSPHERE),
2489                                        getConditionEntryId(Conditions.TOXIC_ATMOSPHERE),
2490                                        getConditionEntryId(Conditions.DENSE_ATMOSPHERE));
2491                makeRelated(getConditionEntryId(Conditions.DECIVILIZED),
2492                                        getConditionEntryId(Conditions.DECIVILIZED_SUBPOP));
2493                makeRelated(getConditionEntryId(Conditions.COLD),
2494                                        getConditionEntryId(Conditions.VERY_COLD));
2495                makeRelated(getConditionEntryId(Conditions.HOT),
2496                                        getConditionEntryId(Conditions.VERY_HOT));
2497                makeRelated(getConditionEntryId(Conditions.LOW_GRAVITY),
2498                                        getConditionEntryId(Conditions.HIGH_GRAVITY));
2499                makeRelated(getConditionEntryId(Conditions.POOR_LIGHT),
2500                                        getConditionEntryId(Conditions.DARK));          
2501                makeRelated(getConditionEntryId(Conditions.HABITABLE),
2502                                        getConditionEntryId(Conditions.MILD_CLIMATE));          
2503                makeRelated(getConditionEntryId(Conditions.TECTONIC_ACTIVITY),
2504                                        getConditionEntryId(Conditions.EXTREME_TECTONIC_ACTIVITY));             
2505                
2506                // link Habitable to all planets that can roll it
2507                CodexEntryPlugin habitable = getEntry(getConditionEntryId(Conditions.HABITABLE));
2508                for (CodexEntryPlugin planet : habitablePlanets) {
2509                        makeRelated(habitable, planet);
2510                }
2511                
2512                
2513//              for (CodexEntryPlugin curr : planets.getChildren()) {
2514//                      if (curr.getParam() instanceof MarketConditionSpecAPI) {
2515//                              MarketConditionSpecAPI spec = (MarketConditionSpecAPI) curr.getParam();
2516//                      }
2517//              }
2518                
2519                
2520                // link all commmodities sharing a demand class - AI cores, survey data, luxury goods/lobster
2521                ListMap<CodexEntryPlugin> commoditiesByDemandClass = new ListMap<>(); 
2522                for (CodexEntryPlugin item : items.getChildren()) {
2523                        if (item.getParam() instanceof CommoditySpecAPI) {
2524                                CommoditySpecAPI spec = (CommoditySpecAPI) item.getParam();
2525                                commoditiesByDemandClass.add(spec.getDemandClass(), item);
2526                        }
2527                }
2528                for (CodexEntryPlugin item : commodities.getChildren()) {
2529                        if (item.getParam() instanceof CommoditySpecAPI) {
2530                                CommoditySpecAPI spec = (CommoditySpecAPI) item.getParam();
2531                                commoditiesByDemandClass.add(spec.getDemandClass(), item);
2532                        }
2533                }
2534                for (List<CodexEntryPlugin> list : commoditiesByDemandClass.values()) {
2535                        makeRelated(list);
2536                }
2537                
2538                
2539                // link colony items to related conditions (and gas giants)
2540                // link blueprint packages to contents
2541                List<CodexEntryPlugin> gasGiants = relatedPlanets.getList(gasGiantListId);
2542                for (CodexEntryPlugin item : items.getChildren()) {
2543                        if (item.getParam() instanceof SpecialItemSpecAPI) {
2544                                SpecialItemSpecAPI spec = (SpecialItemSpecAPI) item.getParam();
2545                                if (spec.hasTag(Items.TAG_COLONY_ITEM)) {
2546                                        InstallableItemEffect effect = ItemEffectsRepo.ITEM_EFFECTS.get(spec.getId());
2547                                        Set<String> relatedConditions = effect.getConditionsRelatedToRequirements(null);
2548                                        for (String conditionId : relatedConditions) {
2549                                                CodexEntryPlugin condition = getEntry(getConditionEntryId(conditionId));
2550                                                if (condition != null) {
2551                                                        item.addRelatedEntry(condition);
2552                                                        condition.addRelatedEntry(item);
2553                                                }
2554                                        }
2555                                        List<String> req = effect.getRequirements(null);
2556                                        if (req != null && (req.contains(ItemEffectsRepo.GAS_GIANT) ||
2557                                                        req.contains(ItemEffectsRepo.NOT_A_GAS_GIANT))) {
2558                                                for (CodexEntryPlugin gasGiant : gasGiants) {
2559                                                        item.addRelatedEntry(gasGiant);
2560                                                        gasGiant.addRelatedEntry(item);
2561                                                }
2562                                        }
2563                                } else if (spec.hasTag(Items.TAG_BLUEPRINT_PACKAGE)) {
2564                                        SpecialItemPlugin plugin = spec.getNewPluginInstance(null);
2565                                        plugin.init(null);
2566                                        if (plugin instanceof MultiBlueprintItemPlugin) {
2567                                                MultiBlueprintItemPlugin multi = (MultiBlueprintItemPlugin) plugin;
2568                                                for (String shipId : multi.getProvidedShips()) {
2569                                                        makeRelated(item.getId(), getShipEntryId(shipId));
2570                                                }
2571                                                for (String fighterId : multi.getProvidedFighters()) {
2572                                                        makeRelated(item.getId(), getFighterEntryId(fighterId));
2573                                                }
2574                                                for (String weaponId : multi.getProvidedWeapons()) {
2575                                                        makeRelated(item.getId(), getWeaponEntryId(weaponId));
2576                                                }
2577                                        }
2578                                }
2579                        } else if (item.getParam() instanceof CommoditySpecAPI) {
2580                                CommoditySpecAPI spec = (CommoditySpecAPI) item.getParam();
2581                                // TODO - anything ???
2582                        }
2583                }
2584                
2585                
2586                // link colony items to industries
2587                for (CodexEntryPlugin curr : items.getChildren()) {
2588                        if (curr.getParam() instanceof SpecialItemSpecAPI) {
2589                                SpecialItemSpecAPI spec = (SpecialItemSpecAPI) curr.getParam();
2590                                if (spec.hasTag(Items.TAG_COLONY_ITEM)) {
2591                                        for (String industryId : spec.getParams().split(",")) {
2592                                                makeRelated(curr.getId(), getIndustryEntryId(industryId.trim()));
2593                                        }
2594                                }
2595                        }
2596                }
2597                
2598                
2599                ListMap<String> commodityToResourceConditions = new ListMap<>();
2600                for (String condId : ResourceDepositsCondition.COMMODITY.keySet()) {
2601                        String commodityId = ResourceDepositsCondition.COMMODITY.get(condId);
2602                        commodityToResourceConditions.add(commodityId, condId);
2603                }
2604                
2605                for (CodexEntryPlugin curr : industries.getChildren()) {
2606                        if (curr.getParam() instanceof IndustrySpecAPI) {
2607                                IndustrySpecAPI spec = (IndustrySpecAPI) curr.getParam();
2608                                
2609                                // link industries to commodities they produce/demand
2610                                for (String comId : Global.getSettings().getIndustryDemand(spec.getId())) {
2611                                        makeRelated(curr.getId(), getCommodityEntryId(comId));
2612                                }
2613                                for (String comId : Global.getSettings().getIndustrySupply(spec.getId())) {
2614                                        makeRelated(curr.getId(), getCommodityEntryId(comId));
2615                                        if (!spec.getId().equals(Industries.AQUACULTURE)) {
2616                                                for (String condId : commodityToResourceConditions.get(comId)) {
2617                                                        makeRelated(curr.getId(), getConditionEntryId(condId));
2618                                                }
2619                                        }
2620                                }
2621                                
2622                                // link industries to their entire upgrade chain
2623                                String otherId = spec.getUpgrade();
2624                                Set<String> seen = new HashSet<>(); // handle circular upgrade chain
2625                                while (otherId != null && !seen.contains(otherId)) {
2626                                        seen.add(otherId);
2627                                        makeRelated(curr.getId(), getIndustryEntryId(otherId));
2628                                        IndustrySpecAPI other = Global.getSettings().getIndustrySpec(otherId);
2629                                        otherId = other.getUpgrade();
2630                                }
2631                                otherId = spec.getDowngrade();
2632                                while (otherId != null && !seen.contains(otherId)) {
2633                                        seen.add(otherId);
2634                                        makeRelated(curr.getId(), getIndustryEntryId(otherId));
2635                                        IndustrySpecAPI other = Global.getSettings().getIndustrySpec(otherId);
2636                                        otherId = other.getDowngrade();
2637                                }
2638                        }
2639                }
2640                
2641                makeRelated(getIndustryEntryId(Industries.AQUACULTURE), getConditionEntryId(Conditions.WATER_SURFACE));
2642                makeRelated(getIndustryEntryId(Industries.AQUACULTURE), getPlanetEntryId(Planets.PLANET_WATER));
2643                makeRelated(getIndustryEntryId(Industries.AQUACULTURE), getConditionEntryId(Conditions.VOLTURNIAN_LOBSTER_PENS));
2644                makeRelated(getIndustryEntryId(Industries.AQUACULTURE), getIndustryEntryId(Industries.FARMING));
2645                makeUnrelated(getIndustryEntryId(Industries.FARMING), getConditionEntryId(Conditions.VOLTURNIAN_LOBSTER_PENS));
2646                makeUnrelated(getIndustryEntryId(Industries.FARMING), getConditionEntryId(Conditions.WATER_SURFACE));
2647                makeUnrelated(getIndustryEntryId(Industries.FARMING), getCommodityEntryId(Commodities.LOBSTER));
2648                makeRelated(getConditionEntryId(Conditions.WATER_SURFACE), getPlanetEntryId(Planets.PLANET_WATER));
2649                
2650                // link commodities with their resource conditions
2651                for (String comId : commodityToResourceConditions.keySet()) {
2652                        for (String condId : commodityToResourceConditions.get(comId)) {
2653                                makeRelated(getCommodityEntryId(comId), getConditionEntryId(condId));
2654                        }
2655                }
2656                
2657                // link weapons and related hullmods; fairly manual process, may comment this out later
2658                // "look here are all the missile weapons in the game in related entries" is not very helpful
2659                // so, try to limit it to smaller subsets that are more difficult to obtain using just tags
2660                // and/or just less obvious
2661                for (CodexEntryPlugin weapon : weapons.getChildren()) {
2662                        if (weapon.getParam() instanceof WeaponSpecAPI) {
2663                                WeaponSpecAPI spec = (WeaponSpecAPI) weapon.getParam();
2664                                if (spec.getAIHints().contains(AIHints.SYSTEM) &&
2665                                                spec.getPrimaryRoleStr() != null &&
2666                                                spec.getPrimaryRoleStr().endsWith("(Fighter)")) {
2667                                        continue;
2668                                }
2669                                String id = weapon.getId();
2670                                
2671                                if (spec.usesAmmo() &&
2672                                                (spec.getType() == WeaponType.BALLISTIC || spec.getType() == WeaponType.ENERGY)) {
2673                                        makeRelated(id, getHullmodEntryId(HullMods.MAGAZINES));
2674                                }
2675                                if (spec.usesAmmo() && spec.getAmmoPerSecond() <= 0 && spec.getType() == WeaponType.MISSILE) {
2676                                        //makeRelated(id, getHullModEntryId(HullMods.MISSLERACKS));
2677                                        if (spec.getSize() == WeaponSize.SMALL) {
2678                                                makeRelated(id, getHullmodEntryId(HullMods.MISSILE_AUTOLOADER));
2679                                        }
2680                                }
2681//                              if (spec.isBeam()) {
2682//                                      makeRelated(id, getHullModEntryId(HullMods.ADVANCEDOPTICS));
2683//                                      makeRelated(id, getHullModEntryId(HullMods.HIGH_SCATTER_AMP));
2684//                              }
2685                                //if (spec.getMountType() == WeaponType.BALLISTIC || spec.getMountType() == WeaponType.HYBRID) {
2686                                if (spec.getMountType() == WeaponType.HYBRID && spec.getSize() != WeaponSize.LARGE && 
2687                                                !spec.getAIHints().contains(AIHints.PD)) {
2688                                        makeRelated(id, getHullmodEntryId(HullMods.BALLISTIC_RANGEFINDER));
2689                                }
2690                                
2691//                              if (!spec.isBeam() && 
2692//                                              (spec.getType() == WeaponType.ENERGY || spec.getType() == WeaponType.HYBRID)) {
2693//                                      makeRelated(id, getHullModEntryId(HullMods.COHERER));
2694//                              }
2695                        }
2696                }
2697                
2698                for (CodexEntryPlugin skill : skills.getChildren()) {
2699                        if (skill.getParam() instanceof SkillSpecAPI) {
2700                                String id = skill.getId();
2701                                SkillSpecAPI spec = (SkillSpecAPI) skill.getParam();
2702                                
2703                                // link skill to unlocked hullmods
2704                                for (String hullmodId : spec.getAllHullmodUnlocks()) {
2705                                        makeRelated(id, getHullmodEntryId(hullmodId));
2706                                }
2707                                
2708                                // link skill to unlocked abilities
2709                                for (String abilityId : spec.getAllAbilityUnlocks()) {
2710                                        makeRelated(id, getAbilityEntryId(abilityId));
2711                                }
2712                                
2713                                // link AI-only skills to AI cores
2714                                if (spec.hasTag(Skills.TAG_AI_CORE_ONLY)) {
2715                                        if (spec.isAdminSkill()) {
2716                                                makeRelated(id, getCommodityEntryId(Commodities.ALPHA_CORE));
2717                                        } else {
2718                                                makeRelated(id, getCommodityEntryId(Commodities.ALPHA_CORE));
2719                                                makeRelated(id, getCommodityEntryId(Commodities.BETA_CORE));
2720                                                makeRelated(id, getCommodityEntryId(Commodities.GAMMA_CORE));
2721                                        }
2722                                }
2723                        }
2724                }
2725                
2726                
2727                for (CodexEntryPlugin hullmod : hullmods.getChildren()) {
2728                        if (hullmod.getParam() instanceof HullModSpecAPI) {
2729                                HullModSpecAPI spec = (HullModSpecAPI) hullmod.getParam();
2730                                
2731                                if (spec.hasTag(Tags.HULLMOD_DMOD)) {
2732                                        makeRelated(getSkillEntryId(Skills.DERELICT_CONTINGENT), getHullmodEntryId(spec.getId()));
2733                                        if (spec.hasTag(Tags.HULLMOD_DAMAGE)) {
2734                                                makeRelated(getSkillEntryId(Skills.HULL_RESTORATION), getHullmodEntryId(spec.getId()));
2735                                        }
2736                                }
2737                                
2738                                CargoStackAPI req = spec.getEffect().getRequiredItem();
2739                                if (req != null) {
2740                                        if (req.getType() == CargoItemType.RESOURCES) {
2741                                                makeRelated(getHullmodEntryId(spec.getId()), getCommodityEntryId(req.getCommodityId()));
2742                                        } else if (req.getType() == CargoItemType.SPECIAL) {
2743                                                makeRelated(getHullmodEntryId(spec.getId()), getItemEntryId(req.getSpecialItemSpecIfSpecial().getId()));
2744                                        }
2745                                }
2746                        }
2747                }
2748                
2749                for (CodexEntryPlugin curr : shipSystems.getChildren()) {
2750                        if (curr.getParam() instanceof ShipSystemSpecAPI) {
2751                                ShipSystemSpecAPI spec = (ShipSystemSpecAPI) curr.getParam();
2752                                if (spec.getStatsScript() instanceof EnergyLashActivatedSystem) {
2753                                        makeRelated(getShipSystemEntryId(ShipSystems.ENERGY_LASH), getShipSystemEntryId(spec.getId()));
2754                                }
2755                        }
2756                }
2757                
2758                
2759                // add some custom links between skills/abilities/hullmods/etc
2760                makeRelated(getSkillEntryId(Skills.AUTOMATED_SHIPS), getHullmodEntryId(HullMods.AUTOMATED));
2761                makeRelated(getSkillEntryId(Skills.PHASE_CORPS), getHullmodEntryId(HullMods.PHASE_FIELD));
2762                makeRelated(getSkillEntryId(Skills.CONTAINMENT_PROCEDURES), getAbilityEntryId(Abilities.EMERGENCY_BURN));
2763                makeRelated(getSkillEntryId(Skills.NAVIGATION), getAbilityEntryId(Abilities.SUSTAINED_BURN));
2764                makeRelated(getSkillEntryId(Skills.SENSORS), getAbilityEntryId(Abilities.GO_DARK));
2765                makeRelated(getSkillEntryId(Skills.SENSORS), getAbilityEntryId(Abilities.SENSOR_BURST));
2766                makeRelated(getAbilityEntryId(Abilities.INTERDICTION_PULSE), getAbilityEntryId(Abilities.SENSOR_BURST));
2767                makeRelated(getAbilityEntryId(Abilities.INTERDICTION_PULSE), getAbilityEntryId(Abilities.SUSTAINED_BURN));
2768                makeRelated(getAbilityEntryId(Abilities.INTERDICTION_PULSE), getAbilityEntryId(Abilities.EMERGENCY_BURN));
2769                makeRelated(getAbilityEntryId(Abilities.INTERDICTION_PULSE), getAbilityEntryId(Abilities.TRANSVERSE_JUMP));
2770                
2771                makeRelated(getSkillEntryId(Skills.TACTICAL_DRILLS), getCommodityEntryId(Commodities.MARINES));
2772                makeRelated(getHullmodEntryId(HullMods.GROUND_SUPPORT), getCommodityEntryId(Commodities.MARINES));
2773                makeRelated(getHullmodEntryId(HullMods.ADVANCED_GROUND_SUPPORT), getCommodityEntryId(Commodities.MARINES));
2774                
2775                makeRelated(getHullmodEntryId(HullMods.NEURAL_INTEGRATOR), getHullmodEntryId(HullMods.AUTOMATED));
2776                makeRelated(getSkillEntryId(Skills.NEURAL_LINK), getHullmodEntryId(HullMods.AUTOMATED));
2777                
2778                makeRelated(getSkillEntryId(Skills.NEURAL_LINK), getHullmodEntryId(HullMods.AUTOMATED));
2779                
2780                // phase skimmer and degraded phase skimmer
2781                makeRelated(getShipSystemEntryId("displacer"), getShipSystemEntryId("displacer_degraded"));
2782                
2783                // relate terminator drone and termination sequence
2784                makeRelated(getFighterEntryId("terminator_wing"), getShipSystemEntryId("drone_strike"));
2785                                
2786                makeRelated(getHullmodEntryId(HullMods.VAST_HANGAR), getHullmodEntryId(HullMods.CONVERTED_HANGAR));
2787                makeRelated(getHullmodEntryId(HullMods.DESIGN_COMPROMISES), getHullmodEntryId(HullMods.CONVERTED_HANGAR));
2788
2789                // tech mining and all ruins
2790                makeRelated(getIndustryEntryId(Industries.TECHMINING), getConditionEntryId(Conditions.RUINS_SCATTERED));
2791                makeRelated(getIndustryEntryId(Industries.TECHMINING), getConditionEntryId(Conditions.RUINS_WIDESPREAD));
2792                makeRelated(getIndustryEntryId(Industries.TECHMINING), getConditionEntryId(Conditions.RUINS_EXTENSIVE));
2793                makeRelated(getIndustryEntryId(Industries.TECHMINING), getConditionEntryId(Conditions.RUINS_VAST));
2794                
2795                
2796                String substrateEntryId = getItemEntryId(Items.SHROUDED_SUBSTRATE);
2797                CodexEntryPlugin substrateEntry = getEntry(substrateEntryId);
2798                if (substrateEntry != null) {
2799                        for (CodexEntryPlugin dwellerPart : relatedHulls.get(Tags.DWELLER + "_allParts")) {
2800                                makeRelated(substrateEntry, dwellerPart);
2801                        }
2802                }
2803                makeRelated(getWeaponEntryId("vortex_launcher"), getShipEntryId("shrouded_vortex"));
2804                
2805//              // link factions to things they sell
2806//              for (CodexEntryPlugin curr : factions.getChildren()) {
2807//                      if (curr.getParam() instanceof FactionSpecAPI) {
2808//                              FactionSpecAPI spec = (FactionSpecAPI) curr.getParam();
2809//                              String id = curr.getId();
2810//                              
2811//                              // weapons
2812//                              for (String key : spec.getWeaponSellFrequency().keySet()) {
2813//                                      Float val = spec.getWeaponSellFrequency().get(key);
2814//                                      if (val > 1f) {
2815//                                              makeRelated(id, getWeaponEntryId(key));
2816//                                      }
2817//                              }
2818//                              
2819//                              // fighters
2820//                              for (String key : spec.getFighterSellFrequency().keySet()) {
2821//                                      Float val = spec.getFighterSellFrequency().get(key);
2822//                                      if (val > 1f) {
2823//                                              makeRelated(id, getFighterEntryId(key));
2824//                                      }
2825//                              }
2826//                              
2827//                              // hullmods
2828//                              for (String key : spec.getHullmodSellFrequency().keySet()) {
2829//                                      Float val = spec.getHullmodSellFrequency().get(key);
2830//                                      if (val > 1f) {
2831//                                              makeRelated(id, getHullmodEntryId(key));
2832//                                      }
2833//                              }
2834//                      }
2835//              }
2836        }
2837        
2838        
2839        public static void makeRelated(CodexEntryPlugin ... plugins) {
2840                for (CodexEntryPlugin one : plugins) {
2841                        for (CodexEntryPlugin two : plugins) {
2842                                if (one == two) continue;
2843                                one.addRelatedEntry(two);
2844                                two.addRelatedEntry(one);
2845                        }
2846                }
2847        }
2848        public static void makeRelated(List<CodexEntryPlugin> plugins) {
2849                for (CodexEntryPlugin one : plugins) {
2850                        for (CodexEntryPlugin two : plugins) {
2851                                if (one == two) continue;
2852                                one.addRelatedEntry(two);
2853                                two.addRelatedEntry(one);
2854                        }
2855                }
2856        }
2857        public static void makeRelated(String ... ids) {
2858                for (String id1 : ids) {
2859                        CodexEntryPlugin one = getEntry(id1);
2860                        if (one == null) continue;
2861                        for (String id2 : ids) {
2862                                if (id1 == id2) continue;
2863                                CodexEntryPlugin two = getEntry(id2);
2864                                if (two == null) continue;
2865                                one.addRelatedEntry(two);
2866                                two.addRelatedEntry(one);
2867                        }
2868                }
2869        }
2870        public static void makeUnrelated(String ... ids) {
2871                for (String id1 : ids) {
2872                        CodexEntryPlugin one = getEntry(id1);
2873                        if (one == null) continue;
2874                        for (String id2 : ids) {
2875                                if (id1 == id2) continue;
2876                                CodexEntryPlugin two = getEntry(id2);
2877                                if (two == null) continue;
2878                                one.removeRelatedEntry(two);
2879                                two.removeRelatedEntry(one);
2880                        }
2881                }
2882        }
2883        
2884        public static String getBaseHullIdEvenIfNotRestorableTo(ShipHullSpecAPI spec) {
2885                String baseId = spec.getBaseHullId();
2886                return baseId;          
2887        }
2888        
2889        public static String getBaseHullId(ShipHullSpecAPI spec) {
2890                ShipHullSpecAPI base = spec.getDParentHull();
2891                
2892                if (!spec.isDefaultDHull() && !spec.isRestoreToBase()) {
2893                        base = spec;
2894                }
2895                
2896                if (base == null && spec.isRestoreToBase()) {
2897                        base = spec.getBaseHull();
2898                }
2899                if (base == null) {
2900                        base = spec;
2901                }
2902                
2903                return base.getHullId();
2904        }
2905        
2906        public static String getFleetMemberEntryId(FleetMemberAPI member) {
2907                return getShipEntryId(getFleetMemberBaseHullId(member));
2908        }
2909        
2910        public static String getFleetMemberBaseHullId(FleetMemberAPI member) {
2911                ShipHullSpecAPI spec = member.getHullSpec();
2912                ShipHullSpecAPI base = spec.getDParentHull();
2913                
2914                if (!spec.isDefaultDHull() && !spec.isRestoreToBase()) {
2915                        base = spec;
2916                }
2917                
2918                if (base == null && spec.isRestoreToBase()) {
2919                        base = spec.getBaseHull();
2920                }
2921                if (base == null) {
2922                        base = spec;
2923                }
2924                
2925                return base.getHullId();
2926        }
2927        
2928        
2929        public static String getShipEntryId(String shipId) {
2930                return "codex_hull_" + shipId;
2931        }
2932        public static String getWeaponEntryId(String weaponId) {
2933                return "codex_weapon_" + weaponId;
2934        }
2935        public static String getFighterEntryId(String wingId) {
2936                return "codex_fighter_" + wingId;
2937        }
2938        public static String getShipSystemEntryId(String shipSystemId) {
2939                return "codex_system_" + shipSystemId;
2940        }
2941        public static String getHullmodEntryId(String hullModId) {
2942                return "codex_hullmod_" + hullModId;
2943        }
2944        public static String getPlanetEntryId(String planetId) {
2945                return "codex_planet_" + planetId;
2946        }
2947        public static String getConditionEntryId(String conditionId) {
2948                return "codex_condition_" + conditionId;
2949        }
2950        public static String getItemEntryId(String itemId) {
2951                return "codex_item_" + itemId;
2952        }
2953        public static String getIndustryEntryId(String industryId) {
2954                return "codex_industry_" + industryId;
2955        }
2956        public static String getCommodityEntryId(String commodityId) {
2957                return "codex_commodity_" + commodityId;
2958        }
2959        public static String getFactionEntryId(String factionId) {
2960                return "codex_faction_" + factionId;
2961        }
2962        public static String getMechanicEntryId(String mechanicId) {
2963                return "codex_mechanic_" + mechanicId;
2964        }
2965        public static String getGalleryEntryId(String galleryId) {
2966                return "codex_gallery_" + galleryId;
2967        }
2968        public static String getSkillEntryId(String skillId) {
2969                return "codex_skill_" + skillId;
2970        }
2971        public static String getAbilityEntryId(String abilityId) {
2972                return "codex_ability_" + abilityId;
2973        }
2974        
2975        public static String getIcon(String key) {
2976                return Global.getSettings().getSpriteName("codex", key);
2977        }
2978        
2979        public static CodexEntryPlugin getEntry(String id) {
2980                return ENTRIES.get(id);
2981        }
2982        
2983        public static void rebuildIdToEntryMap() {
2984                ENTRIES.clear();
2985                if (ROOT == null) return;
2986                
2987                ENTRIES.put(ROOT.getId(), ROOT);
2988                rebuildIdToEntryMap(ROOT);
2989        }
2990        
2991        public static void rebuildIdToEntryMap(CodexEntryPlugin curr) {
2992                for (CodexEntryPlugin child : curr.getChildren()) {
2993                        ENTRIES.put(child.getId(), child);
2994                        if (!child.getChildren().isEmpty()) {
2995                                rebuildIdToEntryMap(child);
2996                        }
2997                }
2998        }
2999        
3000        public static boolean codexFullyUnlocked() {
3001//              if (Global.getSettings().isDevMode() && !Global.getSettings().getBoolean("playtestingMode")) {
3002//                      return true;
3003//              }
3004//              if (true) return true;
3005                return Global.getSettings().getBoolean("allCodexEntriesUnlocked");
3006        }
3007        
3008        
3009        
3010        /**
3011         * Returns a list because it could be a station or a ship with modules.
3012         * @param member
3013         * @return
3014         */
3015        public static List<CodexEntryPlugin> createTempFleetMemberEntry(FleetMemberAPI member) {
3016                ShipHullSpecAPI spec = member.getHullSpec();
3017                ShipVariantAPI variant = member.getVariant();
3018                String entryId = UUID.randomUUID().toString();
3019                
3020                boolean station = spec.getHints().contains(ShipTypeHints.STATION);
3021                boolean limited = variant.hasTag(Tags.SHIP_LIMITED_TOOLTIP);
3022                if (!SharedUnlockData.get().isPlayerAwareOfShip(variant.getHullSpec().getRestoredToHullId())) {
3023                        limited |= variant.hasTag(Tags.LIMITED_TOOLTIP_IF_LOCKED);
3024                        limited |= member.getHullSpec().hasTag(Tags.LIMITED_TOOLTIP_IF_LOCKED);
3025                }
3026                boolean limited2 = limited;
3027                
3028                
3029                Description desc = Global.getSettings().getDescription(spec.getDescriptionId(), Type.SHIP);
3030                String entryName = variant.getFullDesignationWithHullNameForShip();
3031                if (!station && member.getShipName() != null && !member.getShipName().isBlank() && !member.isFighterWing()) {
3032                        entryName = member.getShipName() + ", " + entryName;
3033                }
3034                if (limited) entryName = desc.getText2();
3035                
3036                CodexEntryV2 entry = new CodexEntryV2(entryId, entryName, null, member) {
3037                                @Override
3038                                public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
3039                                        //info.addPara(spec.getHullName(), Misc.getBasePlayerColor(), 0f);
3040                                        //info.addPara(member.getShipName() + " (" + spec.getHullName() + ")", Misc.getBasePlayerColor(), 0f);
3041                                        String name = member.getShipName();
3042                                        if (name == null || name.isBlank()) {
3043                                                name = spec.getHullName();
3044                                        }
3045                                        info.addPara(name, Misc.getBasePlayerColor(), 0f);
3046                                        if (limited2) {
3047                                                info.addPara(Misc.ucFirst(desc.getText2().toLowerCase()), Misc.getGrayColor(), 0f);
3048                                        } else if (spec.hasDesignation() && !spec.getDesignation().equals(spec.getHullName())) {
3049                                                //info.addPara(Misc.ucFirst(variant.getFullDesignationForShip().toLowerCase()), Misc.getGrayColor(), 0f);
3050                                                //info.addPara(Misc.ucFirst(spec.getHullNameWithDashClass().toLowerCase()), Misc.getGrayColor(), 0f);
3051                                                info.addPara(Misc.ucFirst(variant.getFullDesignationForShip().toLowerCase()), Misc.getGrayColor(), 0f);
3052                                        } else if (spec.getHints().contains(ShipTypeHints.STATION)) {
3053                                                info.addPara("Station", Misc.getGrayColor(), 0f);
3054                                        } else if (spec.getHints().contains(ShipTypeHints.MODULE)) {
3055                                                info.addPara("Module", Misc.getGrayColor(), 0f);
3056                                        }
3057                                }
3058                                @Override
3059                                public boolean matchesTags(Set<String> tags) {
3060                                        return false;
3061                                }
3062                                @Override
3063                                public boolean isVisible() {
3064                                        // only shows up in related lists because not added to its own parent category
3065                                        return true;
3066                                }
3067                                @Override
3068                                public boolean isLocked() {
3069                                        return false;
3070                                }
3071                };
3072                
3073                linkFleetMemberEntryToRelated(entry, member, true);
3074                
3075                CodexEntryPlugin parent = getEntry(CAT_SHIPS);
3076                if (station) parent = getEntry(CAT_STATIONS);
3077                entry.setParent(parent);
3078                
3079                List<CodexEntryPlugin> result = new ArrayList<>();
3080                result.add(entry);
3081                
3082                List<CodexEntryPlugin> modules = addModulesForVariant(member.getVariant(), false, entry, parent);
3083                result.addAll(modules);
3084                
3085                return result;
3086                
3087        }
3088        
3089        /**
3090         * This method assumes the entry is a specific fleet member with a loadout, and NOT an empty hull.
3091         * Meaning, this entry was added on the fly such as e.g. from a tooltip of a fleet member.
3092         * The linking for empty hull entries is done in the linkRelatedEntries() method.
3093         * 
3094         * @param entry
3095         * @param member
3096         * @param parentOfModuleEntry
3097         * @param parentOfModule
3098         */
3099        public static void linkFleetMemberEntryToRelated(CodexEntryPlugin entry, 
3100                                FleetMemberAPI member, boolean linkCaptainSkills) {
3101                ShipHullSpecAPI spec = member.getHullSpec();
3102                ShipVariantAPI variant = member.getVariant();
3103                CodexEntryPlugin hullEntry = getEntry(getFleetMemberEntryId(member));
3104                if (hullEntry != null) {
3105                        for (CodexEntryPlugin rel : hullEntry.getRelatedEntries()) {
3106                                if (rel.hasTag(TAG_EMPTY_MODULE)) continue;
3107//                              if (rel.getId().equals("codex_hull_station3")) {
3108//                                      System.out.println("Relid: " + rel.getId());
3109//                              }
3110                                // skip all entries linking modules back to their empty hull
3111                                // since we'll be linking to a non-empty hull
3112                                // or, if we're viewing a module directly, don't link to parent hull at all
3113                                // if getParam2() is a fleet member, we don't want to link to it
3114                                if (rel.getParam2() instanceof FleetMemberAPI) {
3115                                        continue;
3116//                                      FleetMemberAPI otherMember = (FleetMemberAPI) rel.getParam2();
3117//                                      String h1 = getFleetMemberBaseHullId(otherMember);
3118//                                      String h2 = getFleetMemberBaseHullId(parentOfModule);
3119//                                      if (h1.equals(h2)) continue;
3120                                }
3121                                entry.addRelatedEntry(rel);
3122                                rel.addRelatedEntry(entry);
3123                        }
3124                }
3125                
3126                String sysId = spec.getShipSystemId();
3127                if (sysId != null && !sysId.isBlank()) {
3128                        String key = getShipSystemEntryId(sysId);
3129                        CodexEntryPlugin sys = getEntry(key);
3130                        if (sys != null) {
3131                                sys.addRelatedEntry(entry);
3132                                entry.addRelatedEntry(sys);
3133                        
3134                                // link ship systems and ships with their drones, if any
3135                                if (sys.getParam() instanceof ShipSystemSpecAPI) {
3136                                        ShipSystemSpecAPI sysSpec = (ShipSystemSpecAPI) sys.getParam();
3137                                        if (sysSpec.getDroneVariant() != null) {
3138                                                ShipVariantAPI drone = Global.getSettings().getVariant(sysSpec.getDroneVariant());
3139                                                if (drone != null) {
3140                                                        String droneHullId = getBaseHullId(drone.getHullSpec());
3141                                                        String droneEntryId = getShipEntryId(droneHullId);
3142                                                        makeRelated(entry.getId(), droneEntryId);
3143                                                        makeRelated(sys.getId(), droneEntryId);
3144                                                }
3145                                        }
3146                                }
3147                        }
3148                }
3149                
3150                
3151                for (String slotId : variant.getFittedWeaponSlots()) {
3152                        String wid = variant.getWeaponId(slotId);
3153                        CodexEntryPlugin other = getEntry(getWeaponEntryId(wid));
3154                        if (other != null) {
3155                                entry.addRelatedEntry(other);
3156                                other.addRelatedEntry(entry);
3157                        }
3158                }
3159                for (String wingId : variant.getFittedWings()) {
3160                        CodexEntryPlugin other = getEntry(getFighterEntryId(wingId));
3161                        if (other != null) {
3162                                entry.addRelatedEntry(other);
3163                                other.addRelatedEntry(entry);
3164                        }
3165                }
3166                for (String hullmodId : variant.getHullMods()) {
3167                        CodexEntryPlugin other = getEntry(getHullmodEntryId(hullmodId));
3168                        if (other != null) {
3169                                entry.addRelatedEntry(other);
3170                                other.addRelatedEntry(entry);
3171                        }
3172                }
3173                
3174                if (linkCaptainSkills && member.getCaptain() != null && !member.getCaptain().isDefault()) {
3175                        for (SkillLevelAPI sl : member.getCaptain().getStats().getSkillsCopy()) {
3176                                if (sl.getLevel() > 0 && sl.getSkill().isCombatOfficerSkill()) {
3177                                        CodexEntryPlugin other = getEntry(getSkillEntryId(sl.getSkill().getId()));
3178                                        if (other != null) {
3179                                                entry.addRelatedEntry(other);
3180                                                other.addRelatedEntry(entry);
3181                                        }
3182                                }
3183                        }
3184                }
3185        }
3186        
3187        public static boolean hasUnlockedEntry(String entryId) {
3188                CodexEntryPlugin entry = getEntry(entryId);
3189                return entry != null && !entry.isLocked();
3190        }
3191        
3192        public static boolean hasUnlockedEntryForShip(String hullId) {
3193                CodexEntryPlugin entry = getEntry(getShipEntryId(hullId));
3194                return entry != null && !entry.isLocked();
3195        }
3196        
3197        public static void unlinkAndRemoveTempEntry(CodexEntryPlugin entry) {
3198                if (entry == null) return;
3199                for (CodexEntryPlugin rel : entry.getRelatedEntries()) {
3200                        rel.removeRelatedEntry(entry.getId());
3201                        entry.removeRelatedEntry(rel.getId());
3202                }
3203                ENTRIES.remove(entry.getId());
3204                
3205                Set<String> remove = new LinkedHashSet<>();
3206                for (String id : SEEN_STATION_MODULES.keySet()) {
3207                        if (SEEN_STATION_MODULES.get(id) == entry) {
3208                                remove.add(id);
3209                        }
3210                }
3211                for (String id : remove) {
3212                        SEEN_STATION_MODULES.remove(id);
3213                }
3214                
3215                if (entry.getParent() != null) {
3216                        entry.getParent().getChildren().remove(entry);
3217                }
3218        }
3219}
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229