/* * Copyright (C) 2006, 2007, 2008, 2010, 2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2006, 2007 Clam <clamisgood@gmail.com> * Copyright (C) 2007, 2009 Quadduc <quadduc@gmail.com> * Copyright (C) 2013, Robert B. Colton * * This file is part of LateralGM. * * LateralGM is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LateralGM 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 General Public License (COPYING) for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.lateralgm.file; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URI; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; import java.util.Vector; import org.lateralgm.file.iconio.ICOFile; import org.lateralgm.main.LGM; import org.lateralgm.main.UpdateSource; import org.lateralgm.main.UpdateSource.UpdateEvent; import org.lateralgm.main.UpdateSource.UpdateListener; import org.lateralgm.main.UpdateSource.UpdateTrigger; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Background; import org.lateralgm.resources.Constants; import org.lateralgm.resources.ExtensionPackages; import org.lateralgm.resources.Font; import org.lateralgm.resources.GameInformation; import org.lateralgm.resources.GameSettings; import org.lateralgm.resources.GameSettings.ColorDepth; import org.lateralgm.resources.GameSettings.Frequency; import org.lateralgm.resources.GameSettings.IncludeFolder; import org.lateralgm.resources.GameSettings.PGameSettings; import org.lateralgm.resources.GameSettings.Priority; import org.lateralgm.resources.GameSettings.ProgressBar; import org.lateralgm.resources.GameSettings.Resolution; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.GmObject.PhysicsShape; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Path; import org.lateralgm.resources.Resource; import org.lateralgm.resources.Room; import org.lateralgm.resources.Script; import org.lateralgm.resources.Shader; import org.lateralgm.resources.Sound; import org.lateralgm.resources.Sound.PSound; import org.lateralgm.resources.Sound.SoundKind; import org.lateralgm.resources.Sound.SoundType; import org.lateralgm.resources.Sprite; import org.lateralgm.resources.Sprite.BBMode; import org.lateralgm.resources.Sprite.MaskShape; import org.lateralgm.resources.Timeline; import org.lateralgm.resources.sub.Constant; import org.lateralgm.resources.sub.Instance; import org.lateralgm.resources.sub.Instance.PInstance; import org.lateralgm.resources.sub.Tile; import org.lateralgm.resources.sub.Tile.PTile; import org.lateralgm.resources.sub.Trigger; public class ProjectFile implements UpdateListener { //Game Settings Enums public static final ColorDepth[] GS_DEPTHS = { ColorDepth.NO_CHANGE,ColorDepth.BIT_16, ColorDepth.BIT_32 }; public static final Map<ColorDepth,Integer> GS_DEPTH_CODE; public static final Resolution[] GS5_RESOLS = { Resolution.RES_640X480,Resolution.RES_800X600, Resolution.RES_1024X768,Resolution.RES_1280X1024,Resolution.NO_CHANGE,Resolution.RES_320X240, Resolution.RES_1600X1200 }; public static final Resolution[] GS_RESOLS = { Resolution.NO_CHANGE,Resolution.RES_320X240, Resolution.RES_640X480,Resolution.RES_800X600,Resolution.RES_1024X768, Resolution.RES_1280X1024,Resolution.RES_1600X1200 }; public static final Map<Resolution,Integer> GS_RESOL_CODE; public static final Frequency[] GS_FREQS = { Frequency.NO_CHANGE,Frequency.FREQ_60, Frequency.FREQ_70,Frequency.FREQ_85,Frequency.FREQ_100,Frequency.FREQ_120 }; public static final Map<Frequency,Integer> GS_FREQ_CODE; public static final Priority[] GS_PRIORITIES = { Priority.NORMAL,Priority.HIGH,Priority.HIGHEST }; public static final Map<Priority,Integer> GS_PRIORITY_CODE; public static final ProgressBar[] GS_PROGBARS = { ProgressBar.NONE,ProgressBar.DEFAULT, ProgressBar.CUSTOM }; public static final Map<ProgressBar,Integer> GS_PROGBAR_CODE; public static final IncludeFolder[] GS_INCFOLDERS = { IncludeFolder.MAIN,IncludeFolder.TEMP }; public static final Map<IncludeFolder,Integer> GS_INCFOLDER_CODE; static { EnumMap<ColorDepth,Integer> m = new EnumMap<ColorDepth,Integer>(ColorDepth.class); for (int i = 0; i < GS_DEPTHS.length; i++) m.put(GS_DEPTHS[i],i); GS_DEPTH_CODE = Collections.unmodifiableMap(m); EnumMap<Resolution,Integer> m2 = new EnumMap<Resolution,Integer>(Resolution.class); for (int i = 0; i < GS_RESOLS.length; i++) m2.put(GS_RESOLS[i],i); GS_RESOL_CODE = Collections.unmodifiableMap(m2); EnumMap<Frequency,Integer> m3 = new EnumMap<Frequency,Integer>(Frequency.class); for (int i = 0; i < GS_FREQS.length; i++) m3.put(GS_FREQS[i],i); GS_FREQ_CODE = Collections.unmodifiableMap(m3); EnumMap<Priority,Integer> m4 = new EnumMap<Priority,Integer>(Priority.class); for (int i = 0; i < GS_PRIORITIES.length; i++) m4.put(GS_PRIORITIES[i],i); GS_PRIORITY_CODE = Collections.unmodifiableMap(m4); EnumMap<ProgressBar,Integer> m5 = new EnumMap<ProgressBar,Integer>(ProgressBar.class); for (int i = 0; i < GS_PROGBARS.length; i++) m5.put(GS_PROGBARS[i],i); GS_PROGBAR_CODE = Collections.unmodifiableMap(m5); EnumMap<IncludeFolder,Integer> m6 = new EnumMap<IncludeFolder,Integer>(IncludeFolder.class); for (int i = 0; i < GS_INCFOLDERS.length; i++) m6.put(GS_INCFOLDERS[i],i); GS_INCFOLDER_CODE = Collections.unmodifiableMap(m6); } public static final Class<?>[] RESOURCE_KIND = { null,GmObject.class,Sprite.class,Sound.class, Room.class,null,Background.class,Script.class,Path.class,Font.class,GameInformation.class, GameSettings.class,Timeline.class,ExtensionPackages.class,Shader.class }; public static final Map<Class<?>,Integer> RESOURCE_CODE; static { Map<Class<?>,Integer> m = new HashMap<Class<?>,Integer>(); for (int i = 0; i < RESOURCE_KIND.length; i++) if (RESOURCE_KIND[i] != null) m.put(RESOURCE_KIND[i],i); RESOURCE_CODE = Collections.unmodifiableMap(m); } public static final PSound[] SOUND_FX_FLAGS = { PSound.CHORUS,PSound.ECHO,PSound.FLANGER, PSound.GARGLE,PSound.REVERB }; public static final SoundKind[] SOUND_KIND = { SoundKind.NORMAL,SoundKind.BACKGROUND, SoundKind.SPATIAL,SoundKind.MULTIMEDIA }; public static final SoundType[] SOUND_TYPE = { SoundType.MONO, SoundType.STEREO, SoundType.THREED }; public static final Map<SoundKind,Integer> SOUND_KIND_CODE; static { EnumMap<SoundKind,Integer> m = new EnumMap<SoundKind,Integer>(SoundKind.class); for (int i = 0; i < SOUND_KIND.length; i++) m.put(SOUND_KIND[i],i); SOUND_KIND_CODE = Collections.unmodifiableMap(m); } public static final Map<SoundType,Integer> SOUND_TYPE_CODE; static { EnumMap<SoundType,Integer> m = new EnumMap<SoundType,Integer>(SoundType.class); for (int i = 0; i < SOUND_TYPE.length; i++) m.put(SOUND_TYPE[i],i); SOUND_TYPE_CODE = Collections.unmodifiableMap(m); } public static final PhysicsShape[] PHYSICS_SHAPE = { PhysicsShape.CIRCLE,PhysicsShape.BOX, PhysicsShape.SHAPE }; public static final Map<PhysicsShape,Integer> SHAPE_CODE; static { EnumMap<PhysicsShape,Integer> m = new EnumMap<PhysicsShape,Integer>(PhysicsShape.class); for (int i = 0; i < PHYSICS_SHAPE.length; i++) m.put(PHYSICS_SHAPE[i],i); SHAPE_CODE = Collections.unmodifiableMap(m); } public static final BBMode[] SPRITE_BB_MODE = { BBMode.AUTO,BBMode.FULL,BBMode.MANUAL }; public static final Map<BBMode,Integer> SPRITE_BB_CODE; static { EnumMap<BBMode,Integer> m = new EnumMap<BBMode,Integer>(BBMode.class); for (int i = 0; i < SPRITE_BB_MODE.length; i++) m.put(SPRITE_BB_MODE[i],i); SPRITE_BB_CODE = Collections.unmodifiableMap(m); } public static final MaskShape[] SPRITE_MASK_SHAPE = { MaskShape.PRECISE,MaskShape.RECTANGLE, MaskShape.DISK,MaskShape.DIAMOND }; public static final Map<MaskShape,Integer> SPRITE_MASK_CODE; static { EnumMap<MaskShape,Integer> m = new EnumMap<MaskShape,Integer>(MaskShape.class); for (int i = 0; i < SPRITE_MASK_SHAPE.length; i++) m.put(SPRITE_MASK_SHAPE[i],i); m.put(MaskShape.POLYGON,m.get(MaskShape.RECTANGLE)); SPRITE_MASK_CODE = Collections.unmodifiableMap(m); } public String getPath() { return uri.getPath().replace("\\","/"); } // This will return the top level folder path with name that the // main project file is in. public String getDirectory() { String path = ""; if (uri != null) { path = uri.getPath().replace("\\","/"); } File f = new File(path); if (f.exists()) { return f.getParent(); } return path; } public FormatFlavor format; public URI uri; public static interface ResourceHolder<T extends Resource<T,?>> { T getResource(); } public static class SingletonResourceHolder<T extends Resource<T,?>> implements ResourceHolder<T> { T r; public SingletonResourceHolder(T r) { this.r = r; } public T getResource() { return r; } } @SuppressWarnings("unchecked") public class ResourceMap extends HashMap<Class<?>,ResourceHolder<?>> { private static final long serialVersionUID = 1L; public <R extends Resource<R,?>>ResourceHolder<R> put(Class<R> key, ResourceHolder<R> value) { return (ResourceHolder<R>) super.put(key,value); } public <R extends Resource<R,?>>ResourceHolder<R> get(Class<R> key) { return (ResourceHolder<R>) super.get(key); } public <R extends InstantiableResource<R,?>>ResourceList<R> getList(Class<R> key) { return (ResourceList<R>) super.get(key); } @SuppressWarnings("rawtypes") public void addList(Class<?> kind) { put(kind,new ResourceList(kind)); } } public final ResourceMap resMap; public SortedMap<Integer,Trigger> triggers = new TreeMap<Integer,Trigger>(); public List<String> packages = new ArrayList<String>(); public Constants defaultConstants = new Constants(); public GameInformation gameInfo = new GameInformation(); public Vector<GameSettings> gameSettings = new Vector<GameSettings>(); public ExtensionPackages extPackages = new ExtensionPackages(); public int lastInstanceId = 100000; public int lastTileId = 10000000; private final UpdateTrigger updateTrigger = new UpdateTrigger(); public final UpdateSource updateSource = new UpdateSource(this,updateTrigger); public static class FormatFlavor { public static final String GM_OWNER = "GM"; public static final FormatFlavor GM_530 = new FormatFlavor(GM_OWNER,530); public static final FormatFlavor GM_600 = new FormatFlavor(GM_OWNER,600); public static final FormatFlavor GM_701 = new FormatFlavor(GM_OWNER,701); public static final FormatFlavor GM_800 = new FormatFlavor(GM_OWNER,800); public static final FormatFlavor GM_810 = new FormatFlavor(GM_OWNER,810); public static final FormatFlavor GMX_1200 = new FormatFlavor(GM_OWNER,1200); protected Object owner; protected int version; public FormatFlavor(Object owner, int version) { this.owner = owner; this.version = version; } public static FormatFlavor getVersionFlavor(int ver) { switch (ver) { case 530: return FormatFlavor.GM_530; case 600: return FormatFlavor.GM_600; case 701: return FormatFlavor.GM_701; case 800: return FormatFlavor.GM_800; case 810: return FormatFlavor.GM_810; case 1200: return FormatFlavor.GMX_1200; default: return null; } } public Object getOwner() { return owner; } public int getVersion() { return version; } } public ProjectFile() { resMap = new ResourceMap(); for (Class<?> kind : Resource.kinds) if (InstantiableResource.class.isAssignableFrom(kind)) resMap.addList(kind); // Default initial configuration GameSettings gs = createDefaultConfig(); gs.setName("Default"); gameSettings.add(gs); resMap.put(Constants.class,new SingletonResourceHolder<Constants>(defaultConstants)); resMap.put(GameInformation.class,new SingletonResourceHolder<GameInformation>(gameInfo)); // TODO: We don't need this anymore. It should however still be iteratable, perhaps we should // make a Config resource to manage all game configurations? - Robert //resMap.put(GameSettings.class,new SingletonResourceHolder<GameSettings>(gs)); resMap.put(ExtensionPackages.class,new SingletonResourceHolder<ExtensionPackages>(extPackages)); for (ResourceHolder<?> rl : resMap.values()) if (rl instanceof ResourceList<?>) ((ResourceList<?>) rl).updateSource.addListener(this); } public static GameSettings createDefaultConfig() { GameSettings gs = new GameSettings(); Random random = new Random(); gs.put(PGameSettings.GAME_ID,random.nextInt(100000001)); random.nextBytes((byte[]) gs.get(PGameSettings.GAME_GUID)); try { String loc = "org/lateralgm/file/default.ico"; InputStream filein; File file = new File(loc); if (!file.exists()) filein = LGM.class.getClassLoader().getResourceAsStream(loc); else filein = new FileInputStream(file); gs.put(PGameSettings.GAME_ICON,new ICOFile(filein)); } catch (Exception ex) { System.err.println(Messages.getString("GmFile.NOICON")); //$NON-NLS-1$ System.err.println(ex.getMessage()); ex.printStackTrace(); } return gs; } public static Calendar gmBaseTime() { Calendar res = Calendar.getInstance(); res.set(1899,11,29,23,59,59); return res; } public static double longTimeToGmTime(long time) { return (time - gmBaseTime().getTimeInMillis()) / 86400000d; } public static String gmTimeToString(double time) { Calendar base = gmBaseTime(); base.setTimeInMillis(base.getTimeInMillis() + ((long) (time * 86400000))); return DateFormat.getDateTimeInstance().format(base.getTime()); } public void defragIds() { Iterator<ResourceHolder<?>> iter = resMap.values().iterator(); while (iter.hasNext()) { ResourceHolder<?> rh = iter.next(); if (rh instanceof ResourceList<?>) ((ResourceList<?>) rh).defragIds(); } lastInstanceId = 100000; lastTileId = 10000000; for (Room r : resMap.getList(Room.class)) { for (Instance j : r.instances) j.properties.put(PInstance.ID,++lastInstanceId); for (Tile j : r.tiles) j.properties.put(PTile.ID,++lastTileId); } } public static List<Constant> copyConstants(List<Constant> source) { List<Constant> dest = new ArrayList<Constant>(source.size()); for (Constant c : source) dest.add(c.copy()); return dest; } public void updated(UpdateEvent e) { updateTrigger.fire(e); } }