diff --git a/src/main/java/com/projectswg/common/data/customization/CustomizationString.java b/src/main/java/com/projectswg/common/data/customization/CustomizationString.java index 8d2eade..7a24d6b 100644 --- a/src/main/java/com/projectswg/common/data/customization/CustomizationString.java +++ b/src/main/java/com/projectswg/common/data/customization/CustomizationString.java @@ -28,21 +28,17 @@ package com.projectswg.common.data.customization; import com.projectswg.common.data.encodables.mongo.MongoData; import com.projectswg.common.data.encodables.mongo.MongoPersistable; -import com.projectswg.common.data.swgfile.ClientFactory; -import com.projectswg.common.data.swgfile.visitors.CustomizationIDManagerData; import com.projectswg.common.encoding.Encodable; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; import me.joshlarson.jlcommon.log.Log; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.*; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -56,16 +52,54 @@ import java.util.function.Function; */ public class CustomizationString implements Encodable, Persistable, MongoPersistable { - private final CustomizationIDManagerData table; - private final Map variables; + private static final Map VAR_NAME_TO_ID = new HashMap<>(); + private static final Map VAR_ID_TO_NAME = new HashMap<>(); + + static { + try (BufferedInputStream bis = new BufferedInputStream(CustomizationString.class.getResourceAsStream("customization_variables.sdb"))) { + StringBuilder buffer = new StringBuilder(); + String key = null; + int c; + while ((c = bis.read()) != -1) { + switch (c) { + case '\t': + key = buffer.toString(); + buffer.setLength(0); + break; + //noinspection HardcodedLineSeparator + case '\r': + //noinspection HardcodedLineSeparator + case '\n': + if (buffer.length() <= 0) + continue; + assert key != null; + VAR_NAME_TO_ID.put(key, Short.valueOf(buffer.toString())); + VAR_ID_TO_NAME.put(Short.valueOf(buffer.toString()), key); + buffer.setLength(0); + break; + default: + buffer.append((char) c); + break; + } + } + if (buffer.length() > 0) { + assert key != null; + VAR_NAME_TO_ID.put(key, Short.valueOf(buffer.toString())); + VAR_ID_TO_NAME.put(Short.valueOf(buffer.toString()), key); + } + } catch (IOException e) { + throw new RuntimeException("could not load customization variables from resources", e); + } + } + + private final Map variables; public CustomizationString() { - this.table = (CustomizationIDManagerData) ClientFactory.getInfoFromFile("customization/customization_id_manager.iff"); this.variables = Collections.synchronizedMap(new LinkedHashMap<>()); // Ordered and synchronized } boolean isEmpty() { - return variables.size() <= 0; + return variables.isEmpty(); } /** @@ -73,7 +107,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist * @return the amount of characters that would require escaping to be valid UTF-8 */ int valuesToEscape() { - return (int) variables.values().stream().filter(CustomizationVariable::isReserved).count(); + return (int) variables.values().stream().filter(CustomizationString::isReserved).count(); } @Override @@ -81,52 +115,44 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist stream.addByte(0); stream.addMap(variables, (entry) -> { stream.addAscii(entry.getKey()); - stream.addInt(entry.getValue().getValue()); // Awful coincidence + stream.addInt(entry.getValue()); }); } @Override public void read(NetBufferStream stream) { stream.getByte(); - stream.getList((i) -> { - variables.put(stream.getAscii(), new CustomizationVariable(stream.getInt())); - }); + stream.getList((i) -> variables.put(stream.getAscii(), stream.getInt())); } @Override public void saveMongo(MongoData data) { - data.putMap("variables", variables, Function.identity(), CustomizationVariable::getValue); + data.putMap("variables", variables, Function.identity()); } @Override public void readMongo(MongoData data) { variables.clear(); - variables.putAll(data.getMap("variables", Integer.class, CustomizationVariable::new)); + variables.putAll(data.getMap("variables", Integer.class)); } - /** - * Puts a new {@link CustomizationVariable} in this {@code CustomizationString}. - * @param name is the variable to set - * @param value is the assigned value - * @return previously assigned {@link CustomizationVariable} value for {@code variableName} - */ - public CustomizationVariable put(String name, CustomizationVariable value) { + public Integer put(String name, int value) { return variables.put(name, value); } - public CustomizationVariable remove(String name) { + public Integer remove(String name) { return variables.remove(name); } - public CustomizationVariable get(String name) { + public Integer get(String name) { return variables.get(name); } - public Map getVariables() { + public Map getVariables() { return Collections.unmodifiableMap(variables); } - public void forEach(BiConsumer consumer) { + public void forEach(BiConsumer consumer) { variables.forEach(consumer); } @@ -138,11 +164,11 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist public String toString() { StringBuilder str = new StringBuilder(); boolean first = true; - for (Entry e : variables.entrySet()) { + for (Entry e : variables.entrySet()) { if (!first) str.append(", "); first = false; - str.append(e.getKey()).append('=').append(e.getValue().getValue()); + str.append(e.getKey()).append('=').append(e.getValue()); } return str.toString(); } @@ -169,7 +195,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist for (short i = 0; i < variableCount; i++) { short variableId = (short) codePoints[position++]; - String variableName = table.getVariableName(variableId); + String variableName = VAR_ID_TO_NAME.get(variableId); if (variableName == null) { // Variable ID matched no variable name. Log.w("Variable ID %d had no name associated", variableId); @@ -177,8 +203,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist continue; } - CustomizationVariable variable = new CustomizationVariable(); - + int variable; int current = codePoints[position++]; if (current == 0xFF) { // This marks an escaped character to follow @@ -186,10 +211,11 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist switch (next) { case 0x01: // Value is 0 - variable.setValue(0); + default: + variable = 0; break; case 0x02: // Value is 255 - variable.setValue(0xFF); + variable = 0xFF; break; case 0x03: // We shouldn't be meeting an end here. Malformed input. Log.w("Unexpected end of text in CustomizationString, assuming corruption!"); @@ -197,7 +223,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist return; } } else { - variable.setValue(current); + variable = current; } variables.put(variableName, variable); @@ -210,7 +236,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist } int escapeFlag = codePoints[position++]; - int endOfText = codePoints[position++]; + int endOfText = codePoints[position]; if (escapeFlag == 0xFF && endOfText != 0x03) { Log.w("Invalid UTF-8 ending for CustomizationString, assuming corruption!"); @@ -233,13 +259,12 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist out.write(variables.size()); variables.forEach((variableName, variable) -> { - short combinedVariable = table.getVariableId(variableName); + short combinedVariable = VAR_NAME_TO_ID.get(variableName); try { writer.write(combinedVariable); // Put variable - int value = variable.getValue(); - switch (value) { + switch (variable) { case 0x00: writer.write(0xFF); // Escape writer.write(0x01); // Put variable value @@ -249,7 +274,7 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist writer.write(0x02); // Put variable value break; default: - writer.write(value); + writer.write(variable); break; } } catch (Exception e) { @@ -291,4 +316,14 @@ public class CustomizationString implements Encodable, Persistable, MongoPersist return length; } + /** + * + * @param value the value to check + * @return {@code true} if the value is reserved by UTF-8 and escaping it + * would be necessary for proper compatibility. + */ + private static boolean isReserved(int value) { + return value == 0x00 || value == 0xFF; + } + } diff --git a/src/main/java/com/projectswg/common/data/customization/CustomizationVariable.java b/src/main/java/com/projectswg/common/data/customization/CustomizationVariable.java deleted file mode 100644 index f924144..0000000 --- a/src/main/java/com/projectswg/common/data/customization/CustomizationVariable.java +++ /dev/null @@ -1,60 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of PSWGCommon. * - * * - * --------------------------------------------------------------------------------* - * * - * PSWGCommon is free software: you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation, either version 3 of the * - * License, or (at your option) any later version. * - * * - * PSWGCommon is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Affero General Public License for more details. * - * * - * You should have received a copy of the GNU Affero General Public License * - * along with PSWGCommon. If not, see . * - ***********************************************************************************/ -package com.projectswg.common.data.customization; - -public class CustomizationVariable { - - private int value; - - public CustomizationVariable() { - } - - public CustomizationVariable(int value) { - this.value = value; - } - - /** - * - * @return {@code true} if the value is reserved by UTF-8 and escaping it - * would be necessary for proper compatibility. - */ - boolean isReserved() { - return value == 0x00 || value == 0xFF; - } - - public int getValue() { - return value; - } - - public void setValue(int value) { - this.value = value; - } - - public String toString() { - return "CustomizationVariable["+value+']'; - } -} diff --git a/src/main/java/com/projectswg/common/data/swgfile/visitors/CustomizationIDManagerData.java b/src/main/java/com/projectswg/common/data/swgfile/visitors/CustomizationIDManagerData.java index 8df7a1c..9b516fc 100644 --- a/src/main/java/com/projectswg/common/data/swgfile/visitors/CustomizationIDManagerData.java +++ b/src/main/java/com/projectswg/common/data/swgfile/visitors/CustomizationIDManagerData.java @@ -31,6 +31,7 @@ import com.projectswg.common.data.swgfile.IffNode; import com.projectswg.common.data.swgfile.SWGFile; import me.joshlarson.jlcommon.log.Log; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -68,6 +69,10 @@ public class CustomizationIDManagerData extends ClientData { } } + public Map getCustomizationVariables() { + return Collections.unmodifiableMap(customizationVariables); + } + /** * IDs are not zero-indexed - the first ID is 1, not 0. * diff --git a/src/main/resources/com/projectswg/common/data/customization/customization_variables.sdb b/src/main/resources/com/projectswg/common/data/customization/customization_variables.sdb new file mode 100644 index 0000000..e623eea --- /dev/null +++ b/src/main/resources/com/projectswg/common/data/customization/customization_variables.sdb @@ -0,0 +1,121 @@ +private/alternate_shader_blade 118 +/private/index_accel_max 90 +/private/index_accel_min 91 +/private/index_age 36 +/private/index_auto_level 97 +/private/index_banking 88 +/private/index_color_0 34 +/private/index_color_107 70 +/private/index_color_1 2 +/private/index_color_2 1 +/private/index_color_3 29 +/private/index_color_4 49 +/private/index_color_5 65 +/private/index_color_blade 74 +/private/index_color_eye 60 +/private/index_color_eyebrow 64 +/private/index_color_eyeshadow 44 +/private/index_color_facial_hair 31 +/private/index_color_fur 75 +/private/index_color_hair 50 +/private/index_color_lips 46 +/private/index_color_pattern 59 +/private/index_color_patterns 99 +/private/index_color_skin_1 117 +/private/index_color_skin_2 116 +/private/index_color_skin 42 +/private/index_color_tat 55 +/private/index_color_tattoo 54 +/private/index_color_test 69 +/private/index_damp_height 93 +/private/index_damp_pitch 98 +/private/index_damp_roll 96 +/private/index_decel 92 +/private/index_glide 85 +/private/index_hover_height 89 +/private/index_patterns 101 +/private/index_skin 119 +/private/index_slope_mod 95 +/private/index_speed_max 87 +/private/index_speed_min 120 +/private/index_strafe 121 +/private/index_style_beard_2 103 +/private/index_style_beard 35 +/private/index_style_eyebrow 30 +/private/index_style_eyebrows 68 +/private/index_style_eyeshadow 45 +/private/index_style_freckles 37 +/private/index_style_hair 100 +/private/index_style_tattoo 53 +/private/index_texture 102 +/private/index_texture_1 38 +/private/index_turn_rate_max 86 +/private/index_turn_rate_min 94 +/shared_owner/blend_asian_0 41 +/shared_owner/blend_brow_0 67 +/shared_owner/blend_brow_1 66 +/shared_owner/blend_cheeks_0 11 +/shared_owner/blend_cheeks_1 17 +/shared_owner/blend_chest_backpack 83 +/shared_owner/blend_chin_0 80 +/shared_owner/blend_chin_1 78 +/shared_owner/blend_chinsize_0 28 +/shared_owner/blend_chinsize_1 27 +/shared_owner/blend_ear_0 62 +/shared_owner/blend_ear_1 63 +/shared_owner/blend_ears_0 25 +/shared_owner/blend_ears_1 26 +/shared_owner/blend_eyedirection_0 12 +/shared_owner/blend_eyedirection_1 14 +/shared_owner/blend_eyeshape_0 33 +/shared_owner/blend_eyeshape_1 32 +/shared_owner/blend_eyesize_0 7 +/shared_owner/blend_eyesize_1 10 +/shared_owner/blend_eyeslant_0 77 +/shared_owner/blend_fat 5 +/shared_owner/blend_flat_chest 43 +/shared_owner/blend_head_0 114 +/shared_owner/blend_head_1 115 +/shared_owner/blend_headsize_0 57 +/shared_owner/blend_headsize_1 58 +/shared_owner/blend_jacket 111 +/shared_owner/blend_jacket_bandolier 84 +/shared_owner/blend_jacket_belt 82 +/shared_owner/blend_jacket_robe 113 +/shared_owner/blend_jaw_0 19 +/shared_owner/blend_jaw_1 18 +/shared_owner/blend_lipfullness_0 23 +/shared_owner/blend_lipfullness_1 24 +/shared_owner/blend_lipfulness_0 104 +/shared_owner/blend_lipfulness_1 105 +/shared_owner/blend_lipwidth_0 21 +/shared_owner/blend_lipwidth_1 22 +/shared_owner/blend_muscle 4 +/shared_owner/blend_nosedepth_0 8 +/shared_owner/blend_nosedepth_1 6 +/shared_owner/blend_noselength_0 13 +/shared_owner/blend_noselength_1 9 +/shared_owner/blend_nosesize_0 52 +/shared_owner/blend_nosesize_1 51 +/shared_owner/blend_nosewidth_0 15 +/shared_owner/blend_nosewidth_1 16 +/shared_owner/blend_robe 109 +/shared_owner/blend_robe_bandolier 81 +/shared_owner/blend_robe_belt 76 +/shared_owner/blend_sensor_0 39 +/shared_owner/blend_sensor_1 40 +/shared_owner/blend_skinny 3 +/shared_owner/index_color_0 56 +/shared_owner/index_color_1 73 +/shared_owner/index_color_2 79 +/shared_owner/index_color_3 72 +/shared_owner/index_color_4 71 +/shared_owner/index_color_pattern 48 +/shared_owner/index_color_skin 20 +/shared_owner/index_style_beard 110 +/shared_owner/index_style_beard_2 112 +/shared_owner/index_style_eyebrow 106 +/shared_owner/index_style_hair 108 +/shared_owner/index_texture 107 +/shared_owner/index_texture_1 47 +/shared_owner/muscle_fat 61 diff --git a/src/test/java/com/projectswg/common/data/customization/TestCustomizationString.java b/src/test/java/com/projectswg/common/data/customization/TestCustomizationString.java index 9dcb275..8cb77ba 100644 --- a/src/test/java/com/projectswg/common/data/customization/TestCustomizationString.java +++ b/src/test/java/com/projectswg/common/data/customization/TestCustomizationString.java @@ -35,32 +35,29 @@ public class TestCustomizationString { @Test public void testPut() { CustomizationString string = new CustomizationString(); - CustomizationVariable variable = new CustomizationVariable(); String key = "test"; - Assert.assertNull(string.put(key, variable)); // Nothing should be replaced because string's empty - Assert.assertEquals(variable, string.put(key, null)); // Same key, so the variable we put earlier should be replaced + Assert.assertNull(string.put(key, 0)); // Nothing should be replaced because string's empty + Assert.assertEquals((Integer) 0, string.put(key, 1)); // Same key, so the variable we put earlier should be replaced } @Test public void testRemove() { CustomizationString string = new CustomizationString(); - CustomizationVariable variable = new CustomizationVariable(); String key = "test"; - string.put(key, variable); + string.put(key, 0); - Assert.assertEquals(variable, string.remove(key)); // Same key, so the variable we put earlier should be returned + Assert.assertEquals((Integer) 0, string.remove(key)); // Same key, so the variable we put earlier should be returned } @Test public void testIsEmpty() { CustomizationString string = new CustomizationString(); - CustomizationVariable variable = new CustomizationVariable(); String key = "test"; Assert.assertTrue(string.isEmpty()); - string.put(key, variable); + string.put(key, 0); Assert.assertFalse(string.isEmpty()); string.remove(key); Assert.assertTrue(string.isEmpty()); @@ -69,19 +66,13 @@ public class TestCustomizationString { @Test public void testGetLength() { CustomizationString string = new CustomizationString(); - CustomizationVariable first = new CustomizationVariable(); - CustomizationVariable second = new CustomizationVariable(); - - first.setValue(7); // Requires no escaping - second.setValue(0xFF); // Requires escaping - Assert.assertEquals(Short.BYTES, string.getLength()); // Should be an empty array at this point - string.put("first", first); + string.put("first", 7); int expected = Short.BYTES + 7; Assert.assertEquals(expected, string.getLength()); - string.put("second", second); + string.put("second", 0xFF); expected += 4; // Two escape characters, an ID and a value Assert.assertEquals(expected, string.getLength()); }