/// <summary>Raised after the game is launched, right before the first update tick. This happens once per game session (unrelated to loading saves). All mods are loaded and initialized at this point, so this is a good time to set up mod integrations.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event data.</param> private void OnGameLaunched(object sender, GameLaunchedEventArgs e) { this.Monitor.Log("Performing init...", LogLevel.Debug); List <Task> tasks = new List <Task>(); var start = DateTime.Now; var helper = this.Helper; #if !DISABLE_SOUND // Load sounds tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Initializing sounds...", LogLevel.Trace); try { Sound = SoundEffect.FromStream(File.OpenRead(Path.Combine(this.Helper.DirectoryPath, "assets", "Sounds", "SundropBeachNight.wav"))); } catch (Exception err) { monitor.Log("Exception loading Sundrop music: " + err, LogLevel.Error); } } })); #endif // Load maps tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Initializing maps...", LogLevel.Trace); foreach (string file in Directory.EnumerateFiles(Path.Combine(this.Helper.DirectoryPath, "assets", "Maps"))) { string ext = Path.GetExtension(file); if (ext == null || !ext.Equals(".tbin")) { continue; } string map = Path.GetFileName(file); if (map == null) { continue; } try { monitor.Log("Map found: " + map, LogLevel.Trace); var mapFile = this.Helper.Content.Load <Map>(Path.Combine("assets", "Maps", map)); foreach (string prop in SystemData.Layers) { if (mapFile.Properties.ContainsKey(prop)) { string rep = mapFile.Properties[prop].ToString().Replace("\n", " ").Replace(";", " ").Replace(" ", " "); while (rep.Contains(" ")) { rep = rep.Replace(" ", " "); } mapFile.Properties[prop] = rep; } } foreach (TileSheet sheet in mapFile.TileSheets) { if (sheet.ImageSource.EndsWith(".png")) { monitor.Log(sheet.ImageSource, LogLevel.Trace); string xnb = sheet.ImageSource.Substring(0, sheet.ImageSource.Length - 4); try { Game1.content.Load <Texture2D>(xnb); sheet.ImageSource = xnb; } catch { Game1.content.Load <Texture2D>(sheet.ImageSource); } } } var mapInst = new GameLocation(this.Helper.Content.GetActualAssetKey(Path.Combine("assets", "Maps", map)), Path.GetFileNameWithoutExtension(map)); this.Maps.Add(map); Tourist.WarpCache.Add(Path.GetFileNameWithoutExtension(map), Tourist.GetSpawnPoints(mapInst, new HashSet <int>() { Tourist.TILE_WARP_DOWN, Tourist.TILE_WARP_LEFT, Tourist.TILE_WARP_RIGHT, Tourist.TILE_WARP_UP })); Tourist.SpawnCache.Add(Path.GetFileNameWithoutExtension(map), Tourist.GetSpawnPoints(mapInst, new HashSet <int>() { Tourist.TILE_ARROW_DOWN, Tourist.TILE_ARROW_LEFT, Tourist.TILE_ARROW_RIGHT, Tourist.TILE_ARROW_UP, Tourist.TILE_BROWSE, Tourist.TILE_SPAWN })); } catch (Exception err) { monitor.Log("Unable to prepare `" + map + "` location, error follows\n" + err, LogLevel.Error); } } } })); tasks.Add(Task.Run(() => { this.Monitor.Log("Mapping tourist graphics...", LogLevel.Trace); Tourist.LoadResources(); })); tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Reading parking data...", LogLevel.Trace); foreach (var map in helper.Content.Load <JObject>("assets/Data/ParkingSpots.json").Properties()) { monitor.Log("Parsing parking data for the `" + map.Name + "` map, found (" + (map.Value as JArray)?.Count + ") parking spaces.", LogLevel.Trace); var spotsArr = map.Value.ToObject <JArray>(); string name = map.Name; var spots = new List <ParkingSpot>(); foreach (var spotArr in spotsArr.Cast <JArray>()) { var values = spotArr.Children().ToArray(); spots.Add(new ParkingSpot(new Vector2(values[0].ToObject <int>(), values[1].ToObject <int>()), (from JValue facing in values[2].ToObject <JArray>() select(Facing) Enum.Parse(typeof(Facing), facing.ToObject <string>())).ToArray())); } ParkingSpots.Add(map.Name, spots.ToArray()); } } })); tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Reading car data...", LogLevel.Trace); SundropCar.CarTypes = helper.Data.ReadJsonFile <List <CarType> >("assets/Data/Cars.json"); if (SundropCar.CarTypes == null) { monitor.Log("Unable to read Cars.json for car data, cars will not be able to spawn!", LogLevel.Error); SundropCar.CarTypes = new List <CarType>(); } else { Parallel.ForEach(SundropCar.CarTypes, car => { car.Base = helper.Content.GetActualAssetKey("assets/TerrainFeatures/Drivables/" + car.Base + ".png"); car.Recolor = helper.Content.GetActualAssetKey("assets/TerrainFeatures/Drivables/" + car.Recolor + ".png"); car.Decals = car.Decals.Select(pair => new KeyValuePair <string, string>(pair.Key, helper.Content.GetActualAssetKey("assets/TerrainFeatures/Drivables/" + pair.Value + ".png"))).ToDictionary(pair => pair.Key, pair => pair.Value); }); } } })); tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Preparing characters...", LogLevel.Trace); // NPC Spawning foreach (var info in this.Helper.Data.ReadJsonFile <CharacterInfo[]>("assets/Data/CharacterSpawns.json")) { try { // ReSharper disable once ObjectCreationAsStatement new NPC(new AnimatedSprite(this.Helper.Content.GetActualAssetKey("assets/Characters/Sprites/" + info.Texture + ".png"), 18, 16, 32), Vector2.Zero, 2, info.Name) { Portrait = this.Helper.Content.Load <Texture2D>("assets/Characters/Portraits/" + info.Texture + ".png"), }; } catch (Exception err) { monitor.Log("Unable to prepare villager by name of `" + info.Name + "` due to a unexpected issue.\n" + err, LogLevel.Error); } } } })); tasks.Add(Task.Run(() => { using (var monitor = new LogBuffer(this.Monitor)) { monitor.Log("Preparing cameos...", LogLevel.Trace); foreach (var info in this.Helper.Data.ReadJsonFile <CharacterInfo[]>("assets/Data/CameoSpawns.json")) { try { // ReSharper disable once ObjectCreationAsStatement new NPC(new AnimatedSprite(this.Helper.Content.GetActualAssetKey("assets/Characters/Cameos/" + info.Name + "Sprite.png"), 18, 16, 32), Vector2.Zero, 2, info.Name) { Portrait = this.Helper.Content.Load <Texture2D>("assets/Characters/Cameos/" + info.Name + "Portrait.png"), }; } catch (Exception err) { monitor.Log("Unable to prepare cameo character for `" + info.Name + "` due to a unexpected issue.\n" + err, LogLevel.Warn); } } } })); tasks.Add(Task.Run(() => { this.Monitor.Log("Registering commands...", LogLevel.Trace); if (Config.DebugFlags.HasFlag(DebugFlags.Functions)) { helper.ConsoleCommands.Add("sundrop_debug", "For debug use only", (cmd, args) => { if (args.Length == 0) { this.Monitor.Log("Debug command should only be used if you are asked to by a Sundrop developer!", LogLevel.Error); return; } switch (args[0]) { case "tourists": SundropCityMod.SpawnTourists(Game1.player.currentLocation, Convert.ToInt32(args[1])); this.Monitor.Log("Executed tourist spawning algorithm.", LogLevel.Alert); break; case "code": this.TriggerDebugCode(); this.Monitor.Log("Debug code triggered.", LogLevel.Alert); break; case "warp": if (args.Length == 1) { Game1.warpFarmer("Town", 100, 58, 1); } else { if (Game1.getLocationFromName(args[1]) == null) { this.Monitor.Log("Unable to warp, target destination does not exist.", LogLevel.Error); break; } Game1.warpFarmer(args[1], Convert.ToSByte(args[2]) + 64, Convert.ToSByte(args[3]) + 64, false); } this.Monitor.Log("Warping player to target.", LogLevel.Alert); break; case "hotelMenu": Game1.activeClickableMenu = new Hotel.Menu(); this.Monitor.Log("Menu for hotel triggered.", LogLevel.Alert); break; #if !DISABLE_SOUND case "playSound": this.Monitor.Log("Playing custom music...", LogLevel.Alert); Game1.stopMusicTrack(Game1.MusicContext.Default); Sound.Play(1, 1, 1); break; case "reportSound": this.Monitor.Log("AudioEngine Wrapper: " + Game1.audioEngine.GetType().Name, LogLevel.Alert); this.Monitor.Log("Wrapper Disposed: " + Game1.audioEngine.IsDisposed, LogLevel.Alert); this.Monitor.Log("AudioEngine Source: " + Game1.audioEngine.Engine.GetType().Name, LogLevel.Alert); this.Monitor.Log("Source Disposed: " + Game1.audioEngine.Engine.IsDisposed, LogLevel.Alert); break; #endif case "map": this.Monitor.Log("Name of current map: " + Game1.currentLocation.Name, LogLevel.Alert); break; case "position": var p = Game1.player.getTileLocationPoint(); this.Monitor.Log($"Current tile position: {p.X}x {p.Y}y", LogLevel.Alert); break; } }); } })); Task.WaitAll(tasks.ToArray()); var end = DateTime.Now; var time = end.Subtract(start); this.Monitor.Log("Init took " + time.TotalMilliseconds + " milliseconds.", LogLevel.Debug); }