001package com.fs.starfarer.api.impl.codex; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.List; 006import java.util.regex.Matcher; 007import java.util.regex.Pattern; 008 009import java.awt.Color; 010 011import org.json.JSONArray; 012import org.json.JSONException; 013import org.json.JSONObject; 014 015import com.fs.starfarer.api.Global; 016import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin; 017import com.fs.starfarer.api.ui.CustomPanelAPI; 018import com.fs.starfarer.api.ui.LabelAPI; 019import com.fs.starfarer.api.ui.TooltipMakerAPI; 020import com.fs.starfarer.api.ui.UIPanelAPI; 021import com.fs.starfarer.api.util.ListMap; 022import com.fs.starfarer.api.util.Misc; 023 024public class CodexTextEntryLoader { 025 026 public static class ParaData { 027 public int fontSize = 0; 028 public int bulletMode = 0; 029 public Color color = Misc.getTextColor(); 030 public String text; 031 public List<String> highlights = new ArrayList<>(); 032 public List<Color> colors = new ArrayList<>(); 033 public String image; 034 public float imageHeight; 035 } 036 037 public static class TextEntry extends CodexEntryV2 { 038 public List<Object> data = new ArrayList<>(); 039 public String parentId; 040 public TextEntry(String id, String title, String icon) { 041 super(id, title, icon); 042 setParam(id); 043 } 044 045 @Override 046 public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) { 047 super.createTitleForList(info, width, mode); 048 } 049 050 @Override 051 public boolean hasCustomDetailPanel() { 052 return true; 053 } 054 055 @Override 056 public void createCustomDetail(CustomPanelAPI panel, UIPanelAPI relatedEntries, CodexDialogAPI codex) { 057 Color color = Misc.getBasePlayerColor(); 058 Color dark = Misc.getDarkPlayerColor(); 059 Color h = Misc.getHighlightColor(); 060 Color g = Misc.getGrayColor(); 061 float opad = 10f; 062 float pad = 3f; 063 float small = 5f; 064 065 float width = panel.getPosition().getWidth(); 066 067 float initPad = 0f; 068 069 float horzBoxPad = 30f; 070 071 // the right width for a tooltip wrapped in a box to fit next to relatedEntries 072 // 290 is the width of the related entries widget, but it may be null 073 float tw = width - 290f - opad - horzBoxPad + 10f; 074 075 TooltipMakerAPI text = panel.createUIElement(tw, 0, false); 076 text.setParaSmallInsignia(); 077 078 int prevBulletMode = 0; 079 for (Object o : data) { 080 if (o instanceof ParaData) { 081 ParaData para = (ParaData) o; 082 083 TooltipMakerAPI curr = text; 084 float outerPad = initPad; 085 if (para.image != null) { 086 float height = para.imageHeight; 087 if (height <= 0f) height = Global.getSettings().getSprite(para.image).getHeight(); 088 curr = text.beginImageWithText(para.image, height); 089 initPad = 0f; 090 } 091 092 if (prevBulletMode != 0 && para.bulletMode != 0) { 093 initPad = 0f; 094 } 095 prevBulletMode = para.bulletMode; 096 097 if (para.fontSize == -1) { 098 curr.setParaFontDefault(); 099 } else if (para.fontSize == 0) { 100 curr.setParaSmallInsignia(); 101 } else if (para.fontSize == 1) { 102 curr.setParaSmallOrbitron(); 103 } else if (para.fontSize == 2) { 104 curr.setParaOrbitronLarge(); 105 } else if (para.fontSize == 3) { 106 curr.setParaOrbitronVeryLarge(); 107 } 108 109 if (para.bulletMode == 0) { 110 curr.setBulletedListMode(null); 111 } else if (para.bulletMode == 1) { 112 curr.setBulletedListMode(BaseIntelPlugin.INDENT); 113 } else if (para.bulletMode == 2) { 114 curr.setBulletedListMode(BaseIntelPlugin.BULLET); 115 } 116 117 LabelAPI label = curr.addPara(para.text, initPad, 118 para.colors.toArray(new Color[0]), para.highlights.toArray(new String[0])); 119 label.setColor(para.color); 120 label.setHighlight(para.highlights.toArray(new String[0])); 121 label.setHighlightColors(para.colors.toArray(new Color[0])); 122 initPad = opad; 123 124 prevBulletMode = para.bulletMode; 125 126 if (para.image != null) { 127 text.addImageWithText(outerPad); 128 } 129 130 if (para.fontSize > 0) { 131 text.addSpacer(-opad + pad); 132 } 133 } 134 } 135 panel.updateUIElementSizeAndMakeItProcessInput(text); 136 137 UIPanelAPI box = panel.wrapTooltipWithBox(text); 138 panel.addComponent(box).inTL(0f, 0f); 139 if (relatedEntries != null) { 140 panel.addComponent(relatedEntries).inTR(0f, 0f); 141 } 142 143 float height = box.getPosition().getHeight(); 144 if (relatedEntries != null) { 145 height = Math.max(height, relatedEntries.getPosition().getHeight()); 146 } 147 panel.getPosition().setSize(width, height); 148 } 149 } 150 151 152 public static ListMap<String> LINKS = new ListMap<>(); 153 public static void linkRelated() { 154 for (String key : LINKS.keySet()) { 155 CodexEntryPlugin entry = CodexDataV2.getEntry(key); 156 for (String key2 : LINKS.getList(key)) { 157 CodexEntryPlugin other = CodexDataV2.getEntry(key2); 158 if (entry != null && other != null) { 159 CodexDataV2.makeRelated(entry, other); 160 } 161 } 162 } 163 LINKS.clear(); 164 } 165 166 public static void loadTextEntries() { 167 try { 168 String csvName = "data/codex/text_codex_entries.csv"; 169 Global.getLogger(CodexTextEntryLoader.class).info("Loading [" + csvName + "]"); 170 JSONArray rows = Global.getSettings().loadCSV(csvName, true); 171 172 for (int i = 0; i < rows.length(); i++) { 173 JSONObject row = rows.getJSONObject(i); 174 String filename = row.getString("filename"); 175 Global.getLogger(CodexTextEntryLoader.class).info("Loading [" + filename + "]"); 176 String contents = Global.getSettings().loadText(filename); 177 Global.getLogger(CodexTextEntryLoader.class).info("Parsing [" + filename + "]"); 178 parseContents(contents, filename); 179 } 180 } catch (IOException e) { 181 throw new RuntimeException(e); 182 } catch (JSONException e) { 183 throw new RuntimeException(e); 184 } 185 } 186 187 188 public static void parseContents(String contents, String filename) { 189 contents = contents.replaceAll("\\r", "").trim(); 190 String [] lines = contents.split("\\n"); 191 192 Color base = Misc.getBasePlayerColor(); 193 Color dark = Misc.getDarkPlayerColor(); 194 195 Color currColor = null; 196 int currFontSize = 0; 197 String currCategory = null; 198 String paraImage = null; 199 int paraImageHeight = 0; 200 201 TextEntry entry = null; 202 int lineNum = 0; 203 for (String line : lines) { 204 lineNum++; 205 if (line.startsWith("#")) continue; 206 if (line.isBlank()) continue; 207 line = line.trim(); 208 209 if (line.startsWith("CATEGORY ")) { 210 line = line.replaceFirst("CATEGORY ", "").trim(); 211 Global.getLogger(CodexTextEntryLoader.class).info("Parsing category [" + line + "]"); 212 String [] arr = line.split("\\|"); 213 TextEntry cat = new TextEntry(arr[0], arr[2], CodexDataV2.getIcon(arr[1])); 214 cat.parentId = arr[3]; 215 cat.setParam(null); 216 CodexDataV2.ENTRIES.put(cat.getId(), cat); 217 CodexEntryPlugin parent = CodexDataV2.getEntry(cat.parentId); 218 if (parent != null) { 219 cat.setParent(parent); 220 parent.addChild(cat); 221 } 222 continue; 223 } 224 225 if (line.startsWith("COLOR ")) { 226 line = line.replaceFirst("COLOR ", "").trim(); 227 currColor = getColor(line); 228 continue; 229 } 230 if (line.startsWith("FONT ")) { 231 line = line.replaceFirst("FONT ", "").trim(); 232 currFontSize = line.toLowerCase().equals("small") ? -1 : 0; 233 continue; 234 } 235 if (line.startsWith("IMAGE ")) { 236 line = line.replaceFirst("IMAGE ", "").trim(); 237 String [] arr = line.split("\\|"); 238 paraImage = arr[0]; 239 if (arr.length > 1) { 240 try { 241 paraImageHeight = Integer.parseInt(arr[1]); 242 } catch (Throwable t) { 243 paraImage = Global.getSettings().getSpriteName(arr[0], arr[1]); 244 if (arr.length > 2) { 245 paraImageHeight = Integer.parseInt(arr[2]); 246 } 247 } 248 } else { 249 paraImageHeight = 0; 250 } 251 continue; 252 } 253 if (line.startsWith("RESET")) { 254 currColor = getColor("text"); 255 currFontSize = 0; 256 continue; 257 } 258 if (line.startsWith("CURRENT_CATEGORY ")) { 259 line = line.replaceFirst("CURRENT_CATEGORY ", "").trim(); 260 currCategory = line; 261 continue; 262 } 263 264 if (line.startsWith("BEGIN ")) { 265 line = line.replaceFirst("BEGIN ", "").trim(); 266 Global.getLogger(CodexTextEntryLoader.class).info("Parsing entry [" + line + "]"); 267 String [] arr = line.split("\\|"); 268 entry = new TextEntry(arr[0], arr[1], null); 269 entry.parentId = currCategory; 270 if (arr.length >= 3) entry.parentId = arr[2]; 271 continue; 272 } 273 274 if (entry == null) { 275 throw new RuntimeException(String.format("Error parsing [%s] line %s, expected to be inside entry", 276 filename, "" + lineNum)); 277 } 278 279 if (line.startsWith("END")) { 280 if (entry.getIcon() == null) { 281 String icon = CodexDataV2.getIcon("generic"); 282 entry.setIcon(icon); 283 } 284 CodexEntryPlugin parent = CodexDataV2.getEntry(entry.parentId); 285 if (parent != null) { 286 entry.setParent(parent); 287 parent.addChild(entry); 288 } 289 entry = null; 290 currColor = null; 291 currFontSize = 0; 292 continue; 293 } 294 295 if (line.startsWith("ICON ")) { 296 line = line.replaceFirst("ICON ", "").trim(); 297 String icon = CodexDataV2.getIcon(line); 298 entry.setIcon(icon); 299 continue; 300 } 301 302 if (line.startsWith("RELATED ")) { 303 line = line.replaceFirst("RELATED ", "").trim(); 304 String [] ids = line.split("\\|"); 305 for (String id : ids) { 306 //entry.addRelatedEntry(id); 307 LINKS.add(entry.getId(), id); 308 } 309 continue; 310 } 311 312 313 ParaData para = new ParaData(); 314 315 Pattern p = Pattern.compile("(?is)^(=+)(.+?)=+$"); 316 Matcher m = p.matcher(line); 317 if (m.find()) { 318 line = m.group(2); 319 para.fontSize = m.group(1).length(); 320 para.color = base; 321 } 322 323 if (line.startsWith("_ ")) { 324 line = line.replaceFirst("_ ", "").trim(); 325 para.bulletMode = 1; 326 } else if (line.startsWith("- ")) { 327 line = line.replaceFirst("- ", "").trim(); 328 para.bulletMode = 2; 329 } 330 331 p = Pattern.compile("(?is)\\{\\{(.*?)\\}\\}"); 332 m = p.matcher(line); 333 334 while (m.find()) { 335 String curr = m.group(1); 336 String replacement = ""; 337 if (curr.startsWith("color:")) { 338 curr = curr.replaceFirst("color:", "").trim(); 339 String [] arr = curr.split("\\|"); 340 Color color = getColor(arr[0]); 341 para.colors.add(color); 342 para.highlights.add(arr[1]); 343 replacement = arr[1]; 344 } else if (curr.startsWith("rel:")) { 345 curr = curr.replaceFirst("rel:", "").trim(); 346 String [] arr = curr.split("\\|"); 347 //entry.addRelatedEntry(arr[0]); 348 LINKS.add(entry.getId(), arr[0]); 349 para.colors.add(base); 350 para.highlights.add(arr[1]); 351 replacement = arr[1]; 352 } else { 353 Color color = Misc.getHighlightColor(); 354 para.colors.add(color); 355 para.highlights.add(curr); 356 replacement = curr; 357 } 358 line = line.replaceFirst(Pattern.quote(m.group(0)), replacement); 359 } 360 361 para.text = line.replaceAll("%", "%%"); 362 if (currColor != null) { 363 para.color = currColor; 364 } 365 if (para.fontSize == 0) { 366 para.fontSize = currFontSize; 367 } 368 369 para.image = paraImage; 370 para.imageHeight = paraImageHeight; 371 paraImage = null; 372 paraImageHeight = 0; 373 374 entry.data.add(para); 375 } 376 377 if (entry != null) { 378 throw new RuntimeException(String.format("Error parsing [%s] file ended while still parsing entry", filename)); 379 } 380 } 381 382 public static Color getColor(String str) { 383 if (Global.getSettings().hasDesignTypeColor(str)) { 384 return Global.getSettings().getDesignTypeColor(str); 385 } 386 387 Color dark = Misc.getDarkPlayerColor(); 388 Color h = Misc.getHighlightColor(); 389 Color bad = Misc.getNegativeHighlightColor(); 390 Color good = Misc.getPositiveHighlightColor(); 391 Color gray = Misc.getGrayColor(); 392 Color textColor = Misc.getTextColor(); 393 Color player = Misc.getBasePlayerColor(); 394 395 Color color = null; 396 if (str.equals("h")) color = h; 397 else if (str.equals("good")) color = good; 398 else if (str.equals("bad")) color = bad; 399 else if (str.equals("gray")) color = gray; 400 else if (str.equals("grey")) color = gray; 401 else if (str.equals("text")) color = textColor; 402 else if (str.equals("player")) color = player; 403 else if (str.equals("blue")) color = player; 404 else if (str.equals("base")) color = player; 405 else if (str.equals("dark")) color = dark; 406 else color = Global.getSettings().getColor(str); 407 if (color == null) { 408 throw new RuntimeException(String.format("Parsing error: color [%s] not found", str)); 409 } 410 return color; 411 } 412 413 414 public static void main(String[] args) { 415 String test = "Testing 100% in text"; 416 test = test.replaceAll("%", "%%"); 417 System.out.println(String.format(test)); 418 419// String line = "Flux generated by taking damage on shields is {{color:h|hard}} flux, meaning that it can not dissipate while shields are up. Flux generated by firing weapons is {{color:h|soft}} flux and can dissipate while shields are up."; 420// Pattern p = Pattern.compile("(?is)\\{\\{(.*?)\\}\\}"); 421// Matcher m = p.matcher(line); 422 423// String line = "===A heading===\n"; 424// line = line.trim(); 425// Pattern p = Pattern.compile("(?is)^(=+)(.+?)=+$"); 426// Matcher m = p.matcher(line); 427// 428// while (m.find()) { 429// String curr = m.group(2); 430// System.out.println("Found: " + curr); 431// } 432 } 433} 434 435 436 437 438 439 440 441 442 443 444 445 446 447