using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using OpenVIII.Kernel; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace OpenVIII { /// /// Battle Speed Settings /// /// [SuppressMessage("ReSharper", "UnusedMember.Global")] public enum BattleSpeed : byte { Fastest = 1, Fast, Normal, Slow, Slowest, } public enum Module : sbyte { Battle = 3, BattleSwirl = 4, Field = 5, FieldDebug = -5, BattleDebug = -3, MovieTest = -9, OvertureDebug = -12, MainMenuDebug = -13, WorldDebug = -17, FaceTest = -20, IconTest = -21, CardTest = -22, FieldModelTest = -51, } public enum ScaleMode { /// /// scale object to have the same height as viewport /// FitVertical, /// /// scale object to have the same width as viewport /// FitHorizontal, /// /// Same as FitVertical unless width is too large, then it becomes FitHorizontal /// FitBoth, /// /// fill the entire viewport /// Stretch } /// /// Speed Mod /// /// [SuppressMessage("ReSharper", "UnusedMember.Global")] public enum SpeedMod : byte { Stop = 0, Slow = 1, Normal = 2, Haste = 3, AlwaysFull = 0xFF //not sure what i should set this too. } [SuppressMessage("ReSharper", "UnusedMember.Global")] public static partial class Memory { #region Fields public const int PreferredViewportHeight = 720; //original resolution I am working on, therefore if user scales it we need to proportionally scale everything public const int PreferredViewportWidth = 1280; public const ScaleMode ScaleMode = OpenVIII.ScaleMode.Stretch; /// /// add. seems to work if colors are pre-blended like if they overlap the colors combined ahead of time. Used for light. /// /// public static readonly BlendState BlendStateAdd = new BlendState { ColorWriteChannels = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red, ColorSourceBlend = Blend.One, //AlphaSourceBlend = Blend.One, ColorDestinationBlend = Blend.One, //AlphaDestinationBlend = Blend.One, ColorBlendFunction = BlendFunction.Add }; /// /// untested add with blend factor. You set the GraphicsDevice.BlendFactor before drawing. /// public static readonly BlendState BlendStateAddBlendFactor = new BlendState { ColorWriteChannels = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red, ColorSourceBlend = Blend.BlendFactor, //AlphaSourceBlend = Blend.One, ColorDestinationBlend = Blend.One, //AlphaDestinationBlend = Blend.One, ColorBlendFunction = BlendFunction.Add }; public static readonly BlendState BlendStateBasicAdd = new BlendState() { ColorSourceBlend = Blend.SourceColor, ColorDestinationBlend = Blend.DestinationColor, ColorBlendFunction = BlendFunction.Add, AlphaSourceBlend = Blend.SourceAlpha, AlphaDestinationBlend = Blend.DestinationAlpha, AlphaBlendFunction = BlendFunction.Add }; public static readonly BlendState BlendStateForceDraw = new BlendState() { ColorSourceBlend = Blend.SourceColor, ColorDestinationBlend = Blend.SourceColor, ColorBlendFunction = BlendFunction.Add, AlphaSourceBlend = Blend.SourceAlpha, AlphaDestinationBlend = Blend.DestinationAlpha, AlphaBlendFunction = BlendFunction.Add, }; /// /// subtract. Used for windows/glass makes color darker of things behind this layer. /// /// public static readonly BlendState BlendStateSubtract = new BlendState { ColorWriteChannels = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red, ColorSourceBlend = Blend.One, //AlphaSourceBlend = Blend.One, ColorDestinationBlend = Blend.One, //AlphaDestinationBlend = Blend.One, ColorBlendFunction = BlendFunction.ReverseSubtract }; public static readonly Dictionary> DicMusic = new Dictionary>(); public static Card.Game CardGame; public static Cards Cards; public static ContentManager Content; public static GraphicModes CurrentGraphicMode; public static IReadOnlyDictionary DrawPointMagic = new Dictionary() { {0, "Cure - Balamb Garden courtyard"}, {1, "Blizzard - Balamb Garden training center"}, {2, "Full-Life - Balamb Garden MD level"}, {3, "Esuna - Balamb Garden library next to the book shelf"}, {4, "Demi - Balamb Garden cafeteria (only during Garden Riot)"}, {5, "Bio - Balamb Garden B2 floor"}, {6, "Thunder - Balamb outside junk shop"}, {7, "Cure - Balamb harbor"}, {8, "Fire - Fire Cavern"}, {9, "Silence - Dollet town square"}, {10, "Blind - Dollet Communications Tower"}, {11, "Scan - Timber Pub Aurora back alley"}, {12, "Cure - Timber outside the pub"}, {13, "Blizzaga - Timber Maniacs Building left room"}, {14, "Haste - Galbadia Garden lobby"}, {15, "Life - Galbadia Garden changing rooms"}, {16, "Shell - Galbadia Garden courtyard"}, {17, "Protect - Galbadia Garden ice rink"}, {18, "Double - Galbadia Garden auditorium"}, {19, "Aura - Outside Galbadia Garden during Garden war"}, {20, "Cure - Timber forests in a Laguna dream"}, {21, "Water - Timber forests in a Laguna dream"}, {22, "Thundara - Deling City park"}, {23, "Zombie - Deling City Sewers"}, {24, "Esuna - Deling City Sewers"}, {25, "Bio - Deling City Sewers"}, {26, "Fira"}, {27, "Berserk - D-District Prison Floor 9 - right cell"}, {28, "Thundaga - D-District Prison Floor 11 - right cell"}, {29, "Aero - Outside D-District Prison"}, {30, "Blizzara - Missile Base - control room"}, {31, "Blind - Missile Base room with G-Soldiers who ask to deliver a message"}, {32, "Full-Life - Missile Base - silo room"}, {33, "Drain - Winhill road south from town square"}, {34, "Dispel - Winhill town square"}, {35, "Curaga - Winhill Laguna's room in the dream"}, {36, "Reflect - Winhill east road"}, {37, "Protect - Tomb of the Unknown King - outside"}, {38, "Float - Tomb of the Unknown King - north room"}, {39, "Cura - Tomb of the Unknown King - east room"}, {40, "Haste - Fishermans Horizon abandoned train station"}, {41, "Shell - Fishermans Horizon junk shop"}, {42, "Regen - Fishermans Horizon overlooking the sun panel"}, {43, "Full-Life - Fishermans Horizon Master Fisherman's fishing spot"}, {44, "Ultima - Fishermans Horizon mayor's house"}, {45, "Thundaga - Great Salt Lake past the dinosaur skeleton"}, {46, "Meteor - Great Salt Lake dinosaur skeleton"}, {47, "Curaga - Esthar city streets near city entrance"}, {48, "Blizzard - Esthar outside palace"}, {49, "Quake - Esthar outside Odine's Lab"}, {50, "Tornado - Esthar shopping mall"}, {51, "Double - Esthar Odine's Lab in a Laguna dream"}, {52, "Pain"}, {53, "Flare - Esthar Odine's Lab in a Laguna dream"}, {54, "Stop - Sorceress Memorial"}, {55, "Stop"}, {56, "Life - Tears' Point entrance"}, {57, "Reflect - Tears' Point middle"}, {58, "Death - Lunatic Pandora Laboratory in a Laguna dream"}, {59, "Holy - Lunatic Pandora near Elevator #1"}, {60, "Silence - Lunatic Pandora"}, {61, "Ultima - Lunatic Pandora"}, {62, "Confuse"}, {63, "Break - Lunatic Pandora on the way to fight Adel"}, {64, "Meteor - Lunatic Pandora entrance"}, {65, "Curaga - Lunatic Pandora elevator room"}, {66, "Slow"}, {67, "Curaga - Edea's Orphanage"}, {68, "Flare"}, {69, "Holy"}, {70, "Sleep - Centra Excavation Site"}, {71, "Confuse - Centra Excavation Site"}, {72, "Aero - Centra Ruins right ladder after the lift"}, {73, "Drain - Centra Ruins platform after the first staircase"}, {74, "Pain - Centra Ruins next to the dome"}, {75, "Thundaga - Trabia Garden in front of the statue"}, {76, "Zombie - Trabia Garden cemetery"}, {77, "Aura - Trabia Garden stage"}, {78, "Ultima - Shumi Village - above ground"}, {79, "Blizzaga - Shumi Village - outside elder's house"}, {80, "Firaga - Shumi Village workshop"}, {81, "Tornado"}, {82, "Holy - White SeeD Ship"}, {83, "Cura - Ragnarok room with a red Propagator"}, {84, "Life - Ragnarok hangar upstairs"}, {85, "Full-Life - Ragnarok room with save point"}, {86, "Dispel - Deep Sea Research Center second level"}, {87, "Esuna - Deep Sea Research Center secret room"}, {88, "Triple - Deep Sea Research Center third screen on the way to Ultima Weapon's lair"}, {89, "Ultima - Deep Sea Research Center fifth screen on the way to Ultima Weapon's lair"}, {90, "Meltdown - Lunar Base room before the escape pods"}, {91, "Meteor - Lunar Base Ellone's room"}, {92, "Haste"}, {93, "Slow"}, {94, "Curaga"}, {95, "Life"}, {96, "Stop"}, {97, "Regen"}, {98, "Double"}, {99, "Triple"}, {100, "Flare - Ultimecia Castle outside"}, {101, "Curaga - Ultimecia Castle storage room"}, {102, "Cura - Ultimecia Castle passageway"}, {103, "Scan"}, {104, "Esuna"}, {105, "Slow - Ultimecia Castle courtyard"}, {106, "Dispel - Ultimecia Castle chapel"}, {107, "Stop - Ultimecia Castle clock tower"}, {108, "Life"}, {109, "Flare"}, {110, "Aura - Ultimecia Castle wine cellar"}, {111, "Holy - Ultimecia Castle treasure room"}, {112, "Meteor"}, {113, "Meltdown - Ultimecia Castle art gallery"}, {114, "Ultima - Ultimecia Castle armory"}, {115, "Full-Life - Ultimecia Castle prison"}, {116, "Triple"}, {117, "Fire"}, {118, "Fire"}, {119, "Fire"}, {120, "Fire"}, {121, "Fire"}, {122, "Fire"}, {123, "Fire"}, {124, "Fire"}, {125, "Fire"}, {126, "Fire"}, {127, "Fire"}, {128, "Cure"}, {129, "Esuna"}, {130, "Thunder"}, {131, "Fira"}, {132, "Thundara"}, {133, "Blizzara"}, {134, "Blizzard"}, {135, "Fire"}, {136, "Cure"}, {137, "Water"}, {138, "Cura"}, {139, "Esuna"}, {140, "Scan"}, {141, "Shell"}, {142, "Haste"}, {143, "Aero"}, {144, "Bio"}, {145, "Life"}, {146, "Demi"}, {147, "Protect"}, {148, "Holy"}, {149, "Thundaga"}, {150, "Stop"}, {151, "Firaga"}, {152, "Regen"}, {153, "Blizzaga"}, {154, "Confuse"}, {155, "Flare"}, {156, "Dispel"}, {157, "Slow"}, {158, "Quake"}, {159, "Curaga"}, {160, "Tornado"}, {161, "Full-Life"}, {162, "Reflect"}, {163, "Aura"}, {164, "Quake"}, {165, "Double"}, {166, "Break"}, {167, "Meteor"}, {168, "Ultima"}, {169, "Triple"}, {170, "Confuse"}, {171, "Blind"}, {172, "Quake"}, {173, "Sleep"}, {174, "Silence"}, {175, "Flare"}, {176, "Death"}, {177, "Drain"}, {178, "Pain"}, {179, "Berserk"}, {180, "Float"}, {181, "Zombie"}, {182, "Meltdown"}, {183, "Ultima"}, {184, "Tornado"}, {185, "Quake"}, {186, "Meteor"}, {187, "Holy"}, {188, "Flare"}, {189, "Aura"}, {190, "Ultima"}, {191, "Triple"}, {192, "Full-Life"}, {193, "Tornado"}, {194, "Quake"}, {195, "Meteor"}, {196, "Holy"}, {197, "Flare"}, {198, "Aura"}, {199, "Ultima"}, {200, "Triple"}, {201, "Full-Life"}, {202, "Tornado"}, {203, "Quake"}, {204, "Meteor"}, {205, "Holy"}, {206, "Flare"}, {207, "Aura"}, {208, "Ultima"}, {209, "Triple"}, {210, "Full-Life"}, {211, "Ultima"}, {212, "Meteor"}, {213, "Holy"}, {214, "Flare"}, {215, "Aura"}, {216, "Ultima"}, {217, "Triple"}, {218, "Full-Life"}, {219, "Meteor"}, {220, "Holy"}, {221, "Triple"}, {222, "Aura"}, {223, "Ultima"}, {224, "Triple"}, {225, "Full-Life"}, {226, "Meteor"}, {227, "Holy"}, {228, "Flare"}, {229, "Aura"}, {230, "Ultima"}, {231, "Triple"}, {232, "Full-Life"}, {233, "Meteor"}, {234, "Triple"}, {235, "Flare"}, {236, "Aura"}, {237, "Ultima"}, {238, "Triple"}, {239, "Full-Life"}, {240, "Meteor"}, {241, "Holy"}, {242, "Flare"}, {243, "Aura"}, {244, "Ultima"}, {245, "Blizzard"}, {246, "Cure"}, {247, "Dispel"}, {248, "Confuse"}, {249, "Meteor"}, {250, "Double"}, {251, "Aura"}, {252, "Holy"}, {253, "Flare"}, {254, "Ultima"}, {255, "Scan"} }; public static bool EnableDumpingData = false; public static Faces Faces; public static List FfccLeftOverTask = new List(); public static Font Font; public static GraphicsDeviceManager Graphics; public static Icons Icons; public static Core.ImGuiRenderer ImGui; public static Task InitTask; public static Input2 Input2; public static bool IsActive = true; public static KernelBin KernelBin; public static Extended.Languages Languages = Extended.Languages.en; public static Log Log; public static ConcurrentQueue MainThreadOnlyActions; /// /// Random number generator seeded with time. /// /// creates global random class for all sort of things public static Random Random; /// /// Battle music pointer. Set by SET BATTLE MUSIC in field module or by world module. Default=6 /// public static int SetBattleMusic = 6; public static VertexPositionTexture[] ShadowGeometry; public static Texture2D ShadowTexture; public static IReadOnlyDictionary SongsOGG = new Dictionary() { {0,"Lose" }, {1,"The Winner" }, {4,"Never Look Back" }, {5,"Don't Be Afraid" }, {7,"Dead End" }, {8,"Starting Up" }, {9,"Intruders" }, {12,"Don't Be Afraid (X-ATM092)" }, {13,"Force Your Way" }, {14,"FITHOS LUSEC WECOS VINOSEC (No Intro)" }, {15,"Unrest" }, {16,"The Stage is Set" }, {17,"The Landing" }, {18,"Love Grows" }, {19,"Waltz for the Moon" }, {20,"Ami" }, {21,"Find Your Way" }, {22,"Julia" }, {23,"FITHOS LUSEC WECOS VINOSEC" }, {24,"SeeD" }, {25,"Tell Me" }, {26,"Balamb GARDEN" }, {27,"Fear" }, {28,"Dance with the Balamb-Fish" }, {29,"Cactus Jack" }, {35,"The Mission" }, {36,"SUCCESSION OF WITCHES" }, {41,"Blue Fields" }, {42,"Breezy" }, {43,"Concert" }, {46,"Timber Owls" }, {47,"Fragments of Memories" }, {48,"Fisherman's Horizon" }, {49,"Heresy" }, {51,"My Mind" }, {52,"Where I Belong" }, {53,"Starting Up (Looped)" }, {54,"Truth" }, {55,"Trust Me" }, {56,"Galbadia GARDEN" }, {57,"Martial Law" }, {58,"Under Her Control" }, {59,"Only a Plank Between One and Perdition" }, {60,"Junction" }, {61,"Roses and Wine" }, {62,"The Man with the Machine Gun" }, {63,"A Sacrifice" }, {64,"ODEKA ke Chocobo" }, {65,"Drifting" }, {66,"Wounded" }, {67,"Jailed" }, {68,"Retaliation" }, {69,"The Oath" }, {70,"Shuffle or Boogie" }, {71,"Rivals" }, {72,"Blue Sky" }, {73,"Premonition" }, {75,"Galbadia GARDEN (No Intro)" }, {76,"Maybe I'm a Lion" }, {77,"The Castle" }, {78,"Movin'" }, {79,"Overture" }, {80,"The Spy" }, {81,"Mods de Chocobo" }, {82,"The Salt Flats" }, {83,"The Residents" }, {84,"Lunatic Pandora" }, {85,"Silence and Motion" }, {86,"Tears of the Moon" }, {88,"Tears of the Moon (Alternate)" }, {89,"Ride On" }, {90,"The Legendary Beast" }, {91,"Slide Show Part 1" }, {92,"Slide Show Part 2" }, {93,"The Extreme" }, {96,"The Successor" }, {97,"Compression of Time" }, {99,"The Landing (No Intro)" }, {512,"The Loser" }, {513,"Eyes on Me" }, {514,"Irish Jig (Concert)" }, {515,"Eyes on Me (Concert)" }, {516,"Movin' (No Intro)" }, {517,"The Landing (Alternate)" }, {518,"The Landing (Alternate - No Intro)" }, {519,"Galbadia GARDEN (Alternate)" }, }; public static IReadOnlyDictionary SongsSGT = new Dictionary() { {0, "Lose" }, {1, "Win" }, {2, "Open" }, {3, "Combat" }, {4, "Run" }, {5, "Battle" }, {6, "Funsui" }, {7, "End" }, {8, "Antenna" }, {9, "Waiting" }, {10, "Ante " }, {11, "Wind" }, {12, "Crab" }, {13, "Battle2" }, {14, "Friend2" }, {15, "Fuan2" }, {16, "March2" }, {17, "Land" }, {18, "Julia" }, {19, "Waltz" }, {20, "Friend " }, {21, "Dungeon" }, {22, "Pianosolo" }, {23, "Parade" }, {24, "March1" }, {25, "Secret" }, {26, "Garden" }, {27, "Fuan " }, {28, "Polka2" }, {29, "Anthem" }, {30, "FlangChorus" }, {31, "DubChorus" }, {32, "SoloChorus" }, {33, "FemaleChorus" }, {34, "Chorus" }, {35, "M7F5" }, {36, "Sorceress" }, {37, "Reet" }, {38, "Soyo" }, {39, "Rouka" }, {40, "Night" }, {41, "Field" }, {42, "Guitar" }, {43, "Concert" }, {44, "Sea" }, {45, "Silent" }, {46, "Resistance" }, {47, "Kaiso" }, {48, "Horizon" }, {49, "Master" }, {50, "Battle2" }, {51, "Rinoa" }, {52, "Trabia" }, {53, "Horizon2" }, {54, "Truth" }, {55, "Prison" }, {56, "GalbadiaGarden" }, {57, "Timber" }, {58, "Galbadia " }, {59, "Pinchi" }, {60, "Scene1" }, {61, "Pub" }, {62, "Bat3" }, {63, "Stage" }, {64, "Choco" }, {65, "White" }, {66, "Majomv" }, {67, "Musho" }, {68, "Missile" }, {69, "Speech" }, {70, "Card" }, {71, "Gomon" }, {72, "Soto" }, {73, "Majobat" }, {74, "Train" }, {75, "Garden2" }, {76, "Bossbat2" }, {77, "LastDungeon" }, {78, "Gafly" }, {79, "Demo" }, {80, "Spy" }, {81, "VoiceDeChocobo" }, {82, "Salt" }, {83, "Alien" }, {84, "Sekichu" }, {85, "Esta" }, {86, "Moonmv" }, {87, "Mdmotor" }, {88, "Moonmv2" }, {89, "Fly" }, {90, "BossBat1" }, {91, "Rag1" }, {92, "Rag2" }, {93, "LastBoss" }, {94, "Lastwhite" }, {95, "Lasbl" }, {96, "Keisho" }, {97, "Compression" }, }; public static SpriteBatch SpriteBatch; public static Strings Strings; public static int Year = 2013; private static ushort _currentMusic; // need to dynamically detect if 2000/2013/2019, maybe need 2000 1.2 as well. private static string _ff8Dir; private static string _ff8DirData; private static int _mainThreadID; private static Module _module = Module.OvertureDebug; private static ushort _previousMusic; /// /// Stores current save state. When you save this is wrote. When you load this is replaced. /// private static Saves.Data _state = new Saves.Data(); #endregion Fields #region Events public static event EventHandler ModuleChangeEvent; #endregion Events #region Enums public enum GraphicModes { OpenGL, DirectX }; #endregion Enums #region Properties public static string[] Arguments { get; set; } public static float BattleStageScale { get; internal set; } = 100f; public static float CameraScale { get; internal set; } = 100f; public static Point Center => new Point(Graphics.GraphicsDevice.Viewport.Width / 2, Graphics.GraphicsDevice.Viewport.Height / 2); public static BattleSpeed CurrentBattleSpeed => State?.Configuration?.BattleSpeed ?? BattleSpeed.Normal; public static TimeSpan DateTimeNow => TimeSpan.FromTicks(DateTime.Now.Ticks); public static TimeSpan ElapsedGameTime => GameTime?.ElapsedGameTime ?? TimeSpan.Zero; /// /// Active battle encounter. Set by field or battle module. You shouldn't change it in-battle. /// public static Battle.Encounters Encounters { get; set; } public static float EnemyCoordinateScale { get; internal set; } = 100f; public static string FF8Dir { get => _ff8Dir; private set => _ff8Dir = value; } public static string FF8DirData { get => _ff8DirData; private set => _ff8DirData = value; } public static string FF8DirDataLang { get; private set; } /// /// Game time value. Could be null check for null. /// public static GameTime GameTime { get; set; } public static bool Initiated { get; private set; } public static Saves.Data InitState { get; private set; } public static bool IsMainThread => Thread.CurrentThread.ManagedThreadId == _mainThreadID; public static bool IsMouseVisible { get; set; } = false; public static Magazine Magazines { get; private set; } public static ItemsInMenu MItems { get; private set; } public static Module Module { get => _module; set { if (_module == value) return; _module = value; ModuleChangeEvent?.Invoke(null, value); } } public static ushort MusicIndex { get { if (DicMusic.Count > 0) { var max = (ushort)DicMusic.Keys.Max(); var min = (ushort)DicMusic.Keys.Min(); while ((_previousMusic > _currentMusic || _previousMusic == ushort.MinValue && _currentMusic == ushort.MaxValue) && !DicMusic.ContainsKey((MusicId)_currentMusic)) { if (max < _currentMusic) { _currentMusic = max; } else { _currentMusic--; } } while (DicMusic.Count > 0 && _previousMusic < _currentMusic && !DicMusic.ContainsKey((MusicId)_currentMusic)) { if (max < _currentMusic) { _currentMusic = min; } else { _currentMusic++; } } return _currentMusic; } else return 0; } set { _previousMusic = _currentMusic; _currentMusic = value; } } public static Saves.Data PrevState { get; set; } public static Saves.Data State { get => _state; set { _state = value; if (_state != null) _state.LoadTime = GameTime?.TotalGameTime ?? new TimeSpan(); } } /// /// If true by the end of Update() will skip the next Draw() /// public static bool SuppressDraw { get; set; } public static bool Threaded { get; private set; } = true; public static CancellationToken Token { get; private set; } public static CancellationTokenSource TokenSource { get; private set; } public static TimeSpan TotalGameTime => GameTime?.TotalGameTime ?? TimeSpan.Zero; #endregion Properties #region Methods public static void Init(GraphicsDeviceManager graphics, SpriteBatch spriteBatch, ContentManager content, string[] arguments) { if (Log == null) Log = new Log(); Log.WriteLine($"{nameof(Memory)} :: {nameof(Init)}"); Log.WriteLine($"{nameof(GraphicsDeviceManager)} :: {graphics}"); Log.WriteLine($"{nameof(GraphicsDeviceManager)} :: {nameof(graphics.GraphicsDevice.Adapter.CurrentDisplayMode)} :: {graphics?.GraphicsDevice.Adapter.CurrentDisplayMode}"); if (graphics != null) foreach (var i in graphics.GraphicsDevice.Adapter.SupportedDisplayModes) Log.WriteLine($"{nameof(GraphicsDeviceManager)} :: {nameof(graphics.GraphicsDevice.Adapter.SupportedDisplayModes)} :: {i}"); //Log.WriteLine($"{nameof(GraphicsDeviceManager)} :: {graphics.GraphicsDevice.Adapter.DeviceName}"); //Log.WriteLine($"{nameof(SpriteBatch)} :: {spriteBatch}"); Log.WriteLine($"{nameof(ContentManager)} :: {content}"); Log.WriteLine($"{nameof(Random)} :: new"); Random = new Random((int)DateTime.Now.Ticks); Log.WriteLine($"{nameof(Memory)} :: {nameof(_mainThreadID)} = {nameof(Thread)} :: {nameof(Thread.CurrentThread)} :: {nameof(Thread.ManagedThreadId)} = {Thread.CurrentThread.ManagedThreadId}"); _mainThreadID = Thread.CurrentThread.ManagedThreadId; Log.WriteLine($"{nameof(Memory)} :: {nameof(MainThreadOnlyActions)}"); MainThreadOnlyActions = new ConcurrentQueue(); FF8Dir = GameLocation.Current.DataPath; void SetData() => FF8DirData = Extended.GetUnixFullPath(Path.Combine(FF8Dir, "Data")); SetData(); var languageSet = false; void setLang(string lang) { switch (lang.ToLower()) { case "2": case "de": Languages = Extended.Languages.de; break; case "0": case "en": Languages = Extended.Languages.en; break; case "3": case "es": Languages = Extended.Languages.es; break; case "1": case "fr": Languages = Extended.Languages.fr; break; case "4": case "it": Languages = Extended.Languages.it; break; case "5": case "jp": Languages = Extended.Languages.jp; break; default: throw new InvalidEnumArgumentException($"{nameof(Memory)}::{nameof(Init)}::{nameof(lang)} ({lang}) is not a supported language code. (de,en,es,fr,it,jp)"); } languageSet = true; } if (arguments != null && arguments.Length > 0) { IEnumerable splitArguments = (from a in arguments where a.Contains('=') select a.Trim().Split(new[] { '=' }, 2)).OrderByDescending(x => x[0], StringComparer.OrdinalIgnoreCase); foreach (var s in splitArguments) { bool test(string @in, ref string @out) { if (!s[0].Equals(@in, StringComparison.OrdinalIgnoreCase)) return false; @out = s[1].Trim(('"')); return true; } var lang = ""; if (test("dir", ref _ff8Dir)) //override ff8 directory { if (!Directory.Exists(_ff8Dir)) throw new DirectoryNotFoundException( $"{nameof(Memory)}::{nameof(Init)}::{nameof(arguments)}::{nameof(_ff8Dir)} ({s[0]}) Cannot find path: \"{_ff8Dir}\""); SetData(); } else if (test("data", ref _ff8DirData)) //override data folder location { if (!Directory.Exists(_ff8DirData)) throw new DirectoryNotFoundException( $"{nameof(Memory)}::{nameof(Init)}::{nameof(arguments)}::{nameof(_ff8DirData)} ({s[0]}) Cannot find path: \"{_ff8DirData}\""); } else if (test("lang", ref lang)) //override language { setLang(lang); } } } Log.WriteLine($"{nameof(Memory)} :: {nameof(FF8Dir)} = {FF8Dir}"); Log.WriteLine($"{nameof(Memory)} :: {nameof(FF8DirData)} = {FF8DirData}"); var langDatPath = Path.Combine(FF8Dir, "lang.dat"); if (!languageSet && File.Exists(langDatPath)) using (var streamReader = new StreamReader( new FileStream(langDatPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), System.Text.Encoding.UTF8)) { var lang = streamReader.ReadLine()?.Trim(); setLang(lang); } Log.WriteLine($"{nameof(Extended)} :: {nameof(Extended.GetLanguageShort)} = {Extended.GetLanguageShort()}"); var testDir = Extended.GetUnixFullPath(Path.Combine(FF8DirData, $"lang-{Extended.GetLanguageShort()}")); FF8DirDataLang = Directory.Exists(testDir) ? testDir : FF8DirData; Log.WriteLine($"{nameof(Memory)} :: {nameof(FF8DirDataLang)} = {FF8DirDataLang}"); Archives.Init(); Graphics = graphics; SpriteBatch = spriteBatch; Content = content; Arguments = arguments; TokenSource = new CancellationTokenSource(); Token = TokenSource.Token; Threaded = false; if (Threaded) { InitTask = new Task(InitTaskMethod, Token); InitTask.Start(); } else InitTaskMethod(Token); } public static int InitTaskMethod(object obj) { Log.WriteLine($"{nameof(Memory)} :: {nameof(Memory)} :: {nameof(Init)}"); var token = (CancellationToken)obj; if (!token.IsCancellationRequested) Strings = new Strings(); // requires strings because it uses an array generated in strings. // saves data will reference kernel_bin. if (!token.IsCancellationRequested) KernelBin = KernelBin.CreateInstance(); var actions = new List() { // this has a soft requirement on kernel_bin. It checks for null so should work without it. () => {MItems = ItemsInMenu.Read(); }, Saves.Init, //loads all save games from steam2013 or cd2000 or steam2019 directories. first come first serve. //TODO allow choosing of which save folder to use. //this initializes the field module, it's worth to have this at the beginning Fields.Initializer.Init, //this initializes the encounters InitDebuggerBattle.Init, }; if (Graphics?.GraphicsDevice != null) // all below require graphics to work. to load textures graphics device needed. { actions.AddRange(new Action[] { //this initializes the fonts and drawing system- holds fonts in-memory () => { Font = new Font(); }, // card images in menu. () => { Cards = Cards.Load(); }, () => { CardGame = new Card.Game(); }, () => { Faces = Faces.Load(); }, () => { Icons = Icons.Load(); }, () => { Magazines = Magazine.Load(); } }); } actions.Add(() => { InitState = Saves.Data.LoadInitOut(); State = InitState?.Clone(); }); var tasks = new List(); if (!token.IsCancellationRequested) { if (Threaded) { tasks.AddRange(from a in actions where !token.IsCancellationRequested select Task.Run(a, token)); Task.WhenAll(tasks.ToArray()).GetAwaiter().GetResult(); } else foreach (var a in actions.Where(a => !token.IsCancellationRequested)) { a.Invoke(); } if (Graphics?.GraphicsDevice != null) // all below require graphics to work. to load textures graphics device needed. { // requires font, faces, and icons. currently cards only used in debug menu. will // have support for cards when added to menu. if (!token.IsCancellationRequested) Menu.InitStaticMembers(); } } //EXE_Offsets test = new EXE_Offsets(); Initiated = true; //ArchiveBase.PurgeCache();//remove files probably no longer needed. return 0; } public static bool ProcessActions(Action[] actions) { if (Threaded) { var tasks = new List(actions.Length); actions.ForEach(x => { if (!Token.IsCancellationRequested) tasks.Add(Task.Run(x, Token)); }); //Some code that cannot be threaded on init. if (!Task.WaitAll(tasks.ToArray(), 10000)) throw new TimeoutException("Task took too long!"); } else actions.ForEach(x => x.Invoke()); return !Token.IsCancellationRequested; } public static bool[] ProcessFunctions(Func[] functions) { if (!Threaded) return functions.Select(x => x.Invoke()).ToArray(); //var tasks = new List>(functions.Length); //functions.ForEach(x => { if (!Token.IsCancellationRequested) tasks.Add(Task.Run(x, Token)); }); var tasks = functions.Select(x => Task.Run(x, Token)); //Some code that cannot be threaded on init. return Task.WhenAll(tasks.ToArray()).GetAwaiter().GetResult(); //if (!Task.WaitAll(tasks.ToArray(), 10000)) // throw new TimeoutException("Task took too long!"); } public static Vector2 Scale(float width = PreferredViewportWidth, float height = PreferredViewportHeight, ScaleMode scaleMode = ScaleMode, int targetX = 0, int targetY = 0) { if (targetX == 0) targetX = Graphics.GraphicsDevice.Viewport.Width; if (targetY == 0) targetY = Graphics.GraphicsDevice.Viewport.Height; var h = targetX / width; var v = targetY / height; switch (scaleMode) { case ScaleMode.FitHorizontal: return new Vector2(h, h); case ScaleMode.FitVertical: return new Vector2(v, v); case ScaleMode.FitBoth: return (v * width > targetX) ? new Vector2(h, h) : new Vector2(v, v); case ScaleMode.Stretch: return new Vector2(h, v); default: throw new ArgumentOutOfRangeException(nameof(scaleMode), scaleMode, null); } } //ogg and sgt files have same 3 digit prefix. public static void SpriteBatchEnd() => SpriteBatch.End(); public static void SpriteBatchStart(BlendState bs = null, SamplerState ss = null) => SpriteBatch.Begin(SpriteSortMode.Deferred, bs ?? BlendState.AlphaBlend, ss ?? SamplerState.PointClamp, Graphics.GraphicsDevice.DepthStencilState); public static void SpriteBatchStartAlpha(SpriteSortMode sortMode = SpriteSortMode.Deferred, SamplerState ss = null, Matrix? tm = null) => SpriteBatch.Begin(sortMode: sortMode, blendState: BlendState.AlphaBlend, samplerState: ss ?? SamplerState.PointClamp, transformMatrix: tm); public static void SpriteBatchStartStencil(SamplerState ss = null) => SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.Opaque, ss ?? SamplerState.PointClamp, Graphics.GraphicsDevice.DepthStencilState); public static void Update() { Action a = null; while (IsMainThread && (MainThreadOnlyActions?.TryDequeue(out a) ?? false)) { a.Invoke(); } for (var i = 0; IsMainThread && i < FfccLeftOverTask.Count; i++) { if (!FfccLeftOverTask[i].IsCompleted) continue; FfccLeftOverTask[i].Dispose(); FfccLeftOverTask.RemoveAt(i--); } } #endregion Methods #region Classes public static class FieldHolder { #region Fields //public static string[] MapList; public static ushort FieldID = 756; public static string[] Fields; #endregion Fields //public static int[] FieldMemory; #region Methods public static string GetString(ushort? inputFieldID = null) => Fields?.ElementAtOrDefault(inputFieldID ?? FieldID); #endregion Methods } #endregion Classes } }