16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
17 */ |
17 */ |
18 |
18 |
19 package org.hedgewars.hedgeroid.Datastructures; |
19 package org.hedgewars.hedgeroid.Datastructures; |
20 |
20 |
21 import java.io.BufferedReader; |
21 import java.util.Map; |
|
22 |
22 import java.io.File; |
23 import java.io.File; |
23 import java.io.FileNotFoundException; |
|
24 import java.io.FileReader; |
|
25 import java.io.FilenameFilter; |
24 import java.io.FilenameFilter; |
26 import java.io.IOException; |
25 import java.io.IOException; |
27 import java.util.ArrayList; |
26 import java.util.ArrayList; |
28 import java.util.Arrays; |
27 import java.util.Arrays; |
29 import java.util.LinkedHashMap; |
28 import java.util.List; |
|
29 import java.util.Map.Entry; |
|
30 import java.util.TreeMap; |
30 |
31 |
31 import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork; |
32 import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork; |
32 import org.xmlpull.v1.XmlPullParser; |
33 import org.ini4j.Ini; |
33 import org.xmlpull.v1.XmlPullParserException; |
34 import org.ini4j.InvalidFileFormatException; |
34 import org.xmlpull.v1.XmlPullParserFactory; |
35 import org.ini4j.Profile.Section; |
35 |
36 |
36 import android.content.Context; |
37 import android.content.Context; |
37 import android.os.Parcel; |
38 import android.os.Parcel; |
38 import android.os.Parcelable; |
39 import android.os.Parcelable; |
|
40 import android.util.Log; |
39 |
41 |
40 public class Scheme implements Parcelable, Comparable<Scheme>{ |
42 public class Scheme implements Parcelable, Comparable<Scheme>{ |
41 |
|
42 public static final String DIRECTORY_SCHEME = "schemes"; |
43 public static final String DIRECTORY_SCHEME = "schemes"; |
43 |
44 private static final Map<String, BasicSettingMeta> basicSettingsMeta = new TreeMap<String, BasicSettingMeta>(); |
44 private String name; |
45 private static final Map<String, GameModMeta> gameModsMeta = new TreeMap<String, GameModMeta>(); |
45 //private ArrayList<Integer> basic; |
46 |
46 private Integer gamemod; |
47 private final String name; |
47 private ArrayList<Integer> basic;; |
48 private final int gamemod; |
48 private static ArrayList<LinkedHashMap<String, ?>> basicflags = new ArrayList<LinkedHashMap<String, ?>>();//TODO why is it static? |
49 private final Map<String, Integer> basic = new TreeMap<String, Integer>(); |
49 public int health; |
50 |
50 |
51 public Scheme(String _name, Map<String, Integer> _basic, int _gamemod) { |
51 public Scheme(String _name, ArrayList<Integer> _basic, int _gamemod){ |
|
52 name = _name; |
52 name = _name; |
53 gamemod = _gamemod; |
53 gamemod = _gamemod; |
54 basic = _basic; |
54 basic.putAll(_basic); |
55 } |
55 } |
56 |
56 |
57 public Scheme(Parcel in){ |
57 public Scheme(Parcel in){ |
58 readFromParcel(in); |
58 name = in.readString(); |
59 } |
59 gamemod = in.readInt(); |
60 |
60 in.readMap(basic, Integer.class.getClassLoader()); |
61 public void sendToEngine(EngineProtocolNetwork epn)throws IOException{ |
61 } |
|
62 |
|
63 public int getHealth() { |
|
64 return basic.get("InitialHealth"); |
|
65 } |
|
66 |
|
67 public void sendToEngine(EngineProtocolNetwork epn) throws IOException{ |
62 epn.sendToEngine(String.format("e$gmflags %d", gamemod)); |
68 epn.sendToEngine(String.format("e$gmflags %d", gamemod)); |
63 |
69 |
64 for(int pos = 0; pos < basic.size(); pos++){ |
70 for(Map.Entry<String, Integer> entry : basic.entrySet()) { |
65 LinkedHashMap<String, ?> basicflag = basicflags.get(pos); |
71 BasicSettingMeta basicflag = basicSettingsMeta.get(entry.getKey()); |
66 |
72 |
67 String command = (String)basicflag.get("command"); |
73 //Health is a special case, it doesn't need to be send |
68 Integer value = basic.get(pos); |
74 //to the engine yet, we'll do that with the other HH info |
69 |
75 if(!basicflag.command.equals("inithealth")){ |
70 if(command.equals("inithealth")){//Health is a special case, it doesn't need to be send |
76 epn.sendToEngine(String.format("%s %d", basicflag.command, entry.getValue())); |
71 health = value; //to the engine yet, we'll do that with the other HH info |
77 } |
72 continue; |
78 } |
73 } |
79 } |
74 |
80 |
75 Boolean checkOverMax = (Boolean) basicflag.get("checkOverMax"); |
|
76 Boolean times1000 = (Boolean) basicflag.get("times1000"); |
|
77 Integer max = (Integer) basicflag.get("max"); |
|
78 |
|
79 if(checkOverMax && value >= max) value = max; |
|
80 if(times1000) value *= 1000; |
|
81 |
|
82 epn.sendToEngine(String.format("%s %d", command, value)); |
|
83 } |
|
84 } |
|
85 public String toString(){ |
81 public String toString(){ |
86 return name; |
82 return name; |
87 } |
83 } |
88 |
84 |
89 |
85 public static List<Scheme> getSchemes(Context c) throws IllegalArgumentException { |
90 public static final int STATE_START = 0; |
86 File schemeDir = new File(c.getFilesDir(), DIRECTORY_SCHEME); |
91 public static final int STATE_ROOT = 1; |
87 File[] files = schemeDir.listFiles(new FilenameFilter() { |
92 public static final int STATE_NAME = 2; |
88 public boolean accept(File dir, String filename) { |
93 public static final int STATE_BASICFLAGS = 3; |
89 return filename.toLowerCase().startsWith("scheme_"); |
94 public static final int STATE_GAMEMOD = 4; |
90 } |
95 public static final int STATE_BASICFLAG_INTEGER = 5; |
91 }); |
96 public static final int STATE_GAMEMOD_TRUE = 6; |
92 if(files == null) files = new File[0]; |
97 public static final int STATE_GAMEMOD_FALSE = 7; |
|
98 |
|
99 public static ArrayList<Scheme> getSchemes(Context c) throws IllegalArgumentException{ |
|
100 String dir = c.getFilesDir().getAbsolutePath() + '/' + DIRECTORY_SCHEME + '/'; |
|
101 String[] files = new File(dir).list(fnf); |
|
102 if(files == null) files = new String[]{}; |
|
103 Arrays.sort(files); |
93 Arrays.sort(files); |
104 ArrayList<Scheme> schemes = new ArrayList<Scheme>(); |
94 List<Scheme> schemes = new ArrayList<Scheme>(); |
105 |
95 |
|
96 for(File file : files) { |
|
97 try { |
|
98 Ini ini = new Ini(file); |
|
99 |
|
100 String name = ini.get("Scheme", "name"); |
|
101 if(name==null) { |
|
102 name = file.getName(); |
|
103 } |
|
104 Section basicSettingsSection = ini.get("BasicSettings"); |
|
105 Section gameModsSection = ini.get("GameMods"); |
|
106 if(basicSettingsSection == null || gameModsSection == null) { |
|
107 Log.e(Scheme.class.getCanonicalName(), "Scheme file "+file+" is missing the BasicSettings or GameMods section - skipping."); |
|
108 continue; |
|
109 } |
|
110 |
|
111 Map<String, Integer> basicSettings = new TreeMap<String, Integer>(); |
|
112 for(Entry<String, BasicSettingMeta> entry : basicSettingsMeta.entrySet()) { |
|
113 String key = entry.getKey(); |
|
114 BasicSettingMeta settingMeta = entry.getValue(); |
|
115 Integer value = null; |
|
116 if(basicSettingsSection.containsKey(key)) { |
|
117 try { |
|
118 value = Integer.valueOf(basicSettingsSection.get(key)); |
|
119 } catch (NumberFormatException e) { |
|
120 // ignore |
|
121 } |
|
122 } |
|
123 |
|
124 if(value==null) { |
|
125 Log.w(Scheme.class.getCanonicalName(), "Scheme file "+file+" setting "+key+" is missing or invalid, using default."); |
|
126 value = settingMeta.def; |
|
127 } |
|
128 |
|
129 if(settingMeta.checkOverMax) { |
|
130 value = Math.min(value, settingMeta.max); |
|
131 } |
|
132 if(settingMeta.times1000) { |
|
133 value *= 1000; |
|
134 } |
|
135 |
|
136 basicSettings.put(key, value); |
|
137 } |
|
138 |
|
139 int gamemods = 0; |
|
140 for(Entry<String, GameModMeta> entry : gameModsMeta.entrySet()) { |
|
141 String key = entry.getKey(); |
|
142 GameModMeta modMeta = entry.getValue(); |
|
143 if(Boolean.parseBoolean(gameModsSection.get(key))) { |
|
144 gamemods |= (1 << modMeta.bitmaskIndex); |
|
145 } |
|
146 } |
|
147 |
|
148 schemes.add(new Scheme(name, basicSettings, gamemods)); |
|
149 } catch (InvalidFileFormatException e) { |
|
150 throw new RuntimeException(e); |
|
151 } catch (IOException e) { |
|
152 throw new RuntimeException(e); |
|
153 } |
|
154 } |
|
155 return schemes; |
|
156 } |
|
157 |
|
158 /** |
|
159 * This method will parse the basic flags from a prespecified ini file. |
|
160 * In the future we could use one provided by the Data folder. |
|
161 */ |
|
162 public static void parseConfiguration(Context c) { |
|
163 File schemeDir = new File(c.getFilesDir(), DIRECTORY_SCHEME); |
|
164 File settingsFile = new File(schemeDir, "basicsettings"); |
|
165 File gameModsFile = new File(schemeDir, "gamemods"); |
|
166 |
106 try { |
167 try { |
107 XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance(); |
168 Ini ini = new Ini(settingsFile); |
108 XmlPullParser xmlPuller = xmlPullFactory.newPullParser(); |
169 for(Entry<String, Section> sectionEntry : ini.entrySet()) { |
109 |
170 basicSettingsMeta.put(sectionEntry.getKey(), new BasicSettingMeta(sectionEntry.getValue())); |
110 for(String file : files){ |
171 } |
111 BufferedReader br = new BufferedReader(new FileReader(dir + file), 1024); |
172 |
112 xmlPuller.setInput(br); |
173 ini = new Ini(gameModsFile); |
113 String name = null; |
174 for(Entry<String, Section> sectionEntry : ini.entrySet()) { |
114 ArrayList<Integer> basic = new ArrayList<Integer>(); |
175 gameModsMeta.put(sectionEntry.getKey(), new GameModMeta(sectionEntry.getValue())); |
115 Integer gamemod = 0; |
176 } |
116 int health = 0; |
177 } catch (InvalidFileFormatException e) { |
117 int mask = 0x000000004; |
178 throw new RuntimeException(e); |
118 |
|
119 int eventType = xmlPuller.getEventType(); |
|
120 int state = STATE_START; |
|
121 while(eventType != XmlPullParser.END_DOCUMENT){ |
|
122 switch(state){ |
|
123 case STATE_START: |
|
124 if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().equals("scheme")) state = STATE_ROOT; |
|
125 else if(eventType != XmlPullParser.START_DOCUMENT) throwException(file, eventType); |
|
126 break; |
|
127 case STATE_ROOT: |
|
128 if(eventType == XmlPullParser.START_TAG){ |
|
129 if(xmlPuller.getName().equals("basicflags")) state = STATE_BASICFLAGS; |
|
130 else if(xmlPuller.getName().toLowerCase().equals("gamemod")) state = STATE_GAMEMOD; |
|
131 else if(xmlPuller.getName().toLowerCase().equals("name")) state = STATE_NAME; |
|
132 else throwException(file, eventType); |
|
133 }else if(eventType == XmlPullParser.END_TAG) state = STATE_START; |
|
134 else throwException(xmlPuller.getText(), eventType); |
|
135 break; |
|
136 case STATE_BASICFLAGS: |
|
137 if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().toLowerCase().equals("integer")) state = STATE_BASICFLAG_INTEGER; |
|
138 else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT; |
|
139 else throwException(file, eventType); |
|
140 break; |
|
141 case STATE_GAMEMOD: |
|
142 if(eventType == XmlPullParser.START_TAG){ |
|
143 if(xmlPuller.getName().toLowerCase().equals("true")) state = STATE_GAMEMOD_TRUE; |
|
144 else if(xmlPuller.getName().toLowerCase().equals("false")) state = STATE_GAMEMOD_FALSE; |
|
145 else throwException(file, eventType); |
|
146 }else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT; |
|
147 else throwException(file, eventType); |
|
148 break; |
|
149 case STATE_NAME: |
|
150 if(eventType == XmlPullParser.TEXT) name = xmlPuller.getText().trim(); |
|
151 else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT; |
|
152 else throwException(file, eventType); |
|
153 break; |
|
154 case STATE_BASICFLAG_INTEGER: |
|
155 if(eventType == XmlPullParser.TEXT) basic.add(Integer.parseInt(xmlPuller.getText().trim())); |
|
156 else if(eventType == XmlPullParser.END_TAG) state = STATE_BASICFLAGS; |
|
157 else throwException(file, eventType); |
|
158 break; |
|
159 case STATE_GAMEMOD_FALSE: |
|
160 if(eventType == XmlPullParser.TEXT) gamemod <<= 1; |
|
161 else if(eventType == XmlPullParser.END_TAG) state = STATE_GAMEMOD; |
|
162 else throwException(file, eventType); |
|
163 break; |
|
164 case STATE_GAMEMOD_TRUE: |
|
165 if(eventType == XmlPullParser.TEXT){ |
|
166 gamemod |= mask; |
|
167 gamemod <<= 1; |
|
168 }else if(eventType == XmlPullParser.END_TAG) state = STATE_GAMEMOD; |
|
169 else throwException(file, eventType); |
|
170 break; |
|
171 } |
|
172 eventType = getEventType(xmlPuller); |
|
173 }//end while(eventtype != END_DOCUMENT |
|
174 schemes.add(new Scheme(name, basic, gamemod)); |
|
175 }//end for(string file : files |
|
176 return schemes; |
|
177 } catch (XmlPullParserException e) { |
|
178 e.printStackTrace(); |
|
179 } catch (FileNotFoundException e) { |
|
180 e.printStackTrace(); |
|
181 } catch (IOException e) { |
179 } catch (IOException e) { |
182 e.printStackTrace(); |
180 throw new RuntimeException(e); |
183 } |
181 } |
184 return new ArrayList<Scheme>();//TODO handle correctly |
|
185 } |
|
186 |
|
187 private static FilenameFilter fnf = new FilenameFilter(){ |
|
188 public boolean accept(File dir, String filename) { |
|
189 return filename.toLowerCase().startsWith("scheme_"); |
|
190 } |
|
191 }; |
|
192 |
|
193 /** |
|
194 * This method will parse the basic flags from a prespecified xml file. |
|
195 * I use a raw xml file rather than one parsed by aatp at compile time |
|
196 * to keep it generic with other frontends, ie in the future we could |
|
197 * use one provided by the Data folder. |
|
198 */ |
|
199 public static void parseBasicFlags(Context c){ |
|
200 String filename = String.format("%s/%s/basicflags", c.getFilesDir().getAbsolutePath(), DIRECTORY_SCHEME); |
|
201 |
|
202 XmlPullParser xmlPuller = null; |
|
203 BufferedReader br = null; |
|
204 try { |
|
205 XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance(); |
|
206 xmlPuller = xmlPullFactory.newPullParser(); |
|
207 br = new BufferedReader(new FileReader(filename), 1024); |
|
208 xmlPuller.setInput(br); |
|
209 |
|
210 int eventType = getEventType(xmlPuller); |
|
211 boolean continueParsing = true; |
|
212 do{ |
|
213 switch(eventType){ |
|
214 |
|
215 case XmlPullParser.START_TAG: |
|
216 if(xmlPuller.getName().toLowerCase().equals("flag")){ |
|
217 basicflags.add(parseFlag(xmlPuller)); |
|
218 }else if(xmlPuller.getName().toLowerCase().equals("basicflags")){ |
|
219 eventType = getEventType(xmlPuller); |
|
220 }else{ |
|
221 skipCurrentTag(xmlPuller); |
|
222 eventType = getEventType(xmlPuller); |
|
223 } |
|
224 break; |
|
225 case XmlPullParser.START_DOCUMENT://ignore all tags not being "flag" |
|
226 case XmlPullParser.END_TAG: |
|
227 case XmlPullParser.TEXT: |
|
228 default: |
|
229 continueParsing = true; |
|
230 case XmlPullParser.END_DOCUMENT: |
|
231 continueParsing = false; |
|
232 } |
|
233 }while(continueParsing); |
|
234 |
|
235 }catch(IOException e){ |
|
236 e.printStackTrace(); |
|
237 }catch (XmlPullParserException e) { |
|
238 e.printStackTrace(); |
|
239 }finally{ |
|
240 if(br != null) |
|
241 try { |
|
242 br.close(); |
|
243 } catch (IOException e) {} |
|
244 } |
|
245 |
|
246 } |
|
247 |
|
248 /* |
|
249 * * Parses a Tag structure from xml as example we use |
|
250 *<flag> |
|
251 * <checkOverMax> |
|
252 * <boolean>false</boolean> |
|
253 * </checkOverMax> |
|
254 *</flag> |
|
255 * |
|
256 * It returns a LinkedHashMap with key/value pairs |
|
257 */ |
|
258 private static LinkedHashMap<String, Object> parseFlag(XmlPullParser xmlPuller)throws XmlPullParserException, IOException{ |
|
259 LinkedHashMap<String, Object> hash = new LinkedHashMap<String, Object>(); |
|
260 |
|
261 int eventType = xmlPuller.getEventType();//Get the event type which triggered this method |
|
262 if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().toLowerCase().equals("flag")){//valid start of flag tag |
|
263 String lcKey = null; |
|
264 String lcType = null; |
|
265 String value = null; |
|
266 |
|
267 eventType = getEventType(xmlPuller);//<checkOverMax> |
|
268 while(eventType == XmlPullParser.START_TAG){ |
|
269 lcKey = xmlPuller.getName();//checkOverMax |
|
270 if(getEventType(xmlPuller) == XmlPullParser.START_TAG){//<boolean> |
|
271 lcType = xmlPuller.getName().toLowerCase(); |
|
272 if(getEventType(xmlPuller) == XmlPullParser.TEXT){ |
|
273 value = xmlPuller.getText(); |
|
274 if(getEventType(xmlPuller) == XmlPullParser.END_TAG && //</boolean> |
|
275 getEventType(xmlPuller) == XmlPullParser.END_TAG){//</checkOverMax> |
|
276 if(lcType.equals("boolean")) hash.put(lcKey, new Boolean(value)); |
|
277 else if(lcType.equals("string"))hash.put(lcKey, value); |
|
278 else if(lcType.equals("integer")){ |
|
279 try{ |
|
280 hash.put(lcKey, new Integer(value)); |
|
281 }catch (NumberFormatException e){ |
|
282 throw new XmlPullParserException("Wrong integer value in xml file"); |
|
283 } |
|
284 }else{ |
|
285 throwException("basicflags", eventType); |
|
286 } |
|
287 }//</boolean> / </checkOverMax> |
|
288 }//if TEXT |
|
289 }//if boolean |
|
290 eventType = getEventType(xmlPuller);//start new loop |
|
291 } |
|
292 eventType = getEventType(xmlPuller);//</flag> |
|
293 } |
|
294 |
|
295 return hash; |
|
296 } |
|
297 |
|
298 private static void skipCurrentTag(XmlPullParser xmlPuller) throws XmlPullParserException, IOException{ |
|
299 int eventType = xmlPuller.getEventType(); |
|
300 if(eventType != XmlPullParser.START_TAG)return; |
|
301 String tag = xmlPuller.getName().toLowerCase(); |
|
302 |
|
303 while(true){ |
|
304 eventType = getEventType(xmlPuller);//getNext() |
|
305 switch(eventType){ |
|
306 case XmlPullParser.START_DOCUMENT://we're inside of a start tag so START_ or END_DOCUMENT is just wrong |
|
307 case XmlPullParser.END_DOCUMENT: |
|
308 throw new XmlPullParserException("invalid xml file"); |
|
309 case XmlPullParser.START_TAG://if we get a new tag recursively handle it |
|
310 skipCurrentTag(xmlPuller); |
|
311 break; |
|
312 case XmlPullParser.TEXT: |
|
313 break; |
|
314 case XmlPullParser.END_TAG: |
|
315 if(!xmlPuller.getName().toLowerCase().equals(tag)){//if the end tag doesn't match the start tag |
|
316 throw new XmlPullParserException("invalid xml file"); |
|
317 }else{ |
|
318 return;//skip completed |
|
319 } |
|
320 |
|
321 } |
|
322 } |
|
323 } |
|
324 |
|
325 /** |
|
326 * Skips whitespaces.. |
|
327 */ |
|
328 private static int getEventType(XmlPullParser xmlPuller)throws XmlPullParserException, IOException{ |
|
329 int eventType = xmlPuller.next(); |
|
330 while(eventType == XmlPullParser.TEXT && xmlPuller.isWhitespace()){ |
|
331 eventType = xmlPuller.next(); |
|
332 } |
|
333 return eventType; |
|
334 } |
|
335 private static void throwException(String file, int eventType){ |
|
336 throw new IllegalArgumentException(String.format("Xml file: %s malformed with error: %d.", file, eventType)); |
|
337 } |
182 } |
338 |
183 |
339 public int describeContents() { |
184 public int describeContents() { |
340 return 0; |
185 return 0; |
341 } |
186 } |
342 |
187 |
343 public void writeToParcel(Parcel dest, int flags) { |
188 public void writeToParcel(Parcel dest, int flags) { |
344 dest.writeString(name); |
189 dest.writeString(name); |
345 dest.writeInt(gamemod); |
190 dest.writeInt(gamemod); |
346 dest.writeList(basic); |
191 dest.writeMap(basic); |
347 } |
|
348 |
|
349 public void readFromParcel(Parcel src){ |
|
350 name = src.readString(); |
|
351 gamemod = src.readInt(); |
|
352 basic = src.readArrayList(ArrayList.class.getClassLoader()); |
|
353 } |
192 } |
354 |
193 |
355 public static final Parcelable.Creator<Scheme> CREATOR = new Parcelable.Creator<Scheme>() { |
194 public static final Parcelable.Creator<Scheme> CREATOR = new Parcelable.Creator<Scheme>() { |
356 public Scheme createFromParcel(Parcel source) { |
195 public Scheme createFromParcel(Parcel source) { |
357 return new Scheme(source); |
196 return new Scheme(source); |