public MunitionArchetype(string datapath, FLDataFile.Section sec, ILogController log) : base(datapath, sec, log) { if (sec.SettingExists("hull_damage")) { HullDamage = sec.GetSetting("hull_damage").Float(0); } if (sec.SettingExists("energy_damage")) { EnergyDamage = sec.GetSetting("energy_damage").Float(0); } if (sec.SettingExists("weapon_type")) { WeaponType = ArchetypeDB.FindWeaponType(sec.GetSetting("weapon_type").Str(0)); if (WeaponType == null) { log.AddLog(LogType.ERROR, "error: weapon_type not found " + sec.Desc); } } if (sec.SettingExists("seeker")) { Seeker = sec.GetSetting("seeker").Str(0); } if (sec.SettingExists("time_to_lock")) { TimeToLock = sec.GetSetting("time_to_lock").Float(0); } if (sec.SettingExists("seeker_range")) { SeekerRange = sec.GetSetting("seeker_range").Float(0); } if (sec.SettingExists("seeker_fov_deg")) { SeekerFovDeg = sec.GetSetting("seeker_fov_deg").Float(0); } if (sec.SettingExists("detonation_dist")) { DetonationDist = sec.GetSetting("detonation_dist").Float(0); } if (sec.SettingExists("cruise_disruptor")) { CruiseDisruptor = sec.GetSetting("cruise_disruptor").Str(0) == "true"; } if (sec.SettingExists("max_angular_velocity")) { MaxAngularVelocity = sec.GetSetting("max_angular_velocity").Float(0); } if (sec.SettingExists("motor")) { uint motorID = FLUtility.CreateID(sec.GetSetting("motor").Str(0)); MotorArch = ArchetypeDB.Find(motorID) as MotorArchetype; if (MotorArch == null) { log.AddLog(LogType.ERROR, "error: motor not found " + sec.Desc); } } }
/// <summary> /// Load base market data and setup the prices for goods at each base. /// </summary> /// <param name="path"></param> private static void LoadBaseMarketData(string path, ILogController log) { var ini = new FLDataFile(path, true); foreach (var sec in ini.Sections) { string sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName != "basegood") { continue; } var basedata = FindBase(sec.GetSetting("base").Str(0)); if (basedata == null) { log.AddLog(LogType.ERROR, "error: " + sec.Desc); continue; } foreach (FLDataFile.Setting set in sec.Settings) { var settingName = set.SettingName.ToLowerInvariant(); if (settingName != "marketgood") { continue; } var nickname = set.Str(0); var level_needed_to_buy = set.Float(1); var reputation_needed_to_buy = set.Float(2); var baseSells = (set.UInt(5) == 1); var basePriceMultiplier = 1.0f; if (set.NumValues() > 6) { basePriceMultiplier = set.Float(6); } var goodid = FLUtility.CreateID(nickname); var good = FindGood(goodid); if (good == null) { log.AddLog(LogType.ERROR, "error: " + set.Desc); continue; } if (baseSells) { basedata.GoodsForSale[goodid] = good.BasePrice * basePriceMultiplier; } basedata.GoodsToBuy[goodid] = good.BasePrice * basePriceMultiplier; } } }
public LauncherArchetype(string datapath, FLDataFile.Section sec, ILogController log) : base(datapath, sec, log) { if (sec.SettingExists("damage_per_fire")) { DamagePerFire = sec.GetSetting("damage_per_fire").Float(0); } if (sec.SettingExists("power_usage")) { PowerUsage = sec.GetSetting("power_usage").Float(0); } if (sec.SettingExists("refire_delay")) { RefireDelay = sec.GetSetting("refire_delay").Float(0); } if (sec.SettingExists("muzzle_velocity")) { MuzzleVelocity = new Vector(0, 0, -sec.GetSetting("muzzle_velocity").Float(0)); } if (sec.SettingExists("projectile_archetype")) { uint projectileArchID = FLUtility.CreateID(sec.GetSetting("projectile_archetype").Str(0)); ProjectileArch = ArchetypeDB.Find(projectileArchID) as ProjectileArchetype; if (ProjectileArch == null) { log.AddLog(LogType.ERROR, "error: projectile not found: " + sec.Desc); } } }
private static void LoadBase(string fldatapath, string path, BaseData basedata, ILogController log) { try { var ini = new FLDataFile(path, true); foreach (FLDataFile.Section sec in ini.Sections) { string sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName == "baseinfo") { basedata.StartRoom = String.Format("{0:x}_{1}", basedata.BaseID, sec.GetSetting("start_room").Str(0)); basedata.StartRoomID = FLUtility.CreateID(basedata.StartRoom); } else if (sectionName == "room") { var room = new Room { Nickname = sec.GetSetting("nickname").Str(0).ToLowerInvariant() }; room.RoomID = FLUtility.CreateID(room.Nickname); path = fldatapath + Path.DirectorySeparatorChar + sec.GetSetting("file").Str(0); //I don't need room data at the moment. Yay. //TODO: make LoadRoom? //LoadRoom(fldatapath, path, basedata, room, log); basedata.Rooms[room.Nickname] = room; } } } catch (Exception e) { log.AddLog(LogType.ERROR, "error: '" + e.Message + "' when parsing '" + path); } }
//TODO: <s>use LoadRoom</s> private static void LoadRoom(string fldatapath, string path, BaseData basedata, Room room, ILogController log) { try { var ini = new FLDataFile(path, true); foreach (var sec in ini.Sections) { var sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName != "room_info") { continue; } if (!sec.SettingExists("set_script")) { continue; } var setScript = sec.GetSetting("set_script").Str(0); var thnText = File.ReadAllText(fldatapath + Path.DirectorySeparatorChar + setScript); var thn = new ThnParse(); thn.Parse(thnText); foreach (var e in thn.entities.Where(e => e.type.ToLowerInvariant() == "marker")) { //TODO: so what } } } catch (Exception e) { log.AddLog(LogType.ERROR, "error: '" + e.Message + "' when parsing '" + path); } }
public void SetState(IPlayerState newstate) { if (_state != newstate) { Log.AddLog(LogType.GENERAL, "change state: old={0} new={1}", _state.StateName(), newstate.StateName()); _state = newstate; _state.EnterState(this); if (_state.StateName() == "in-base-state") { Ship.IsDestroyed = false; } } }
/// <summary> /// Load shop information for equipment and commodities. /// </summary> /// <param name="path"></param> private static void LoadGoodData(string path, ILogController log) { var ini = new FLDataFile(path, true); foreach (FLDataFile.Section sec in ini.Sections) { var sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName != "good") { continue; } var good = new Good { Nickname = sec.GetSetting("nickname").Str(0) }; good.GoodID = FLUtility.CreateID(good.Nickname); string category = sec.GetSetting("category").Str(0); if (category == "equipment") { good.category = Good.Category.Equipment; good.BasePrice = sec.GetSetting("price").Float(0); uint archid = FLUtility.CreateID(sec.GetSetting("equipment").Str(0)); good.EquipmentOrShipArch = ArchetypeDB.Find(archid); } else if (category == "commodity") { good.category = Good.Category.Commodity; good.BasePrice = sec.GetSetting("price").Float(0); uint archid = FLUtility.CreateID(sec.GetSetting("equipment").Str(0)); good.EquipmentOrShipArch = ArchetypeDB.Find(archid); } else if (category == "shiphull") { good.category = Good.Category.ShipHull; good.BasePrice = sec.GetSetting("price").Float(0); uint archid = FLUtility.CreateID(sec.GetSetting("ship").Str(0)); good.EquipmentOrShipArch = ArchetypeDB.Find(archid); } else if (category == "ship") { good.category = Good.Category.ShipPackage; uint goodid = FLUtility.CreateID(sec.GetSetting("hull").Str(0)); good.Shiphull = Goods[goodid]; } else { log.AddLog(LogType.ERROR, "error: unknown category " + sec.Desc); } Goods[good.GoodID] = good; } }
public static void Load(string flPath, ILogController log) { string flIniPath = flPath + Path.DirectorySeparatorChar + "EXE" + Path.DirectorySeparatorChar + "Freelancer.ini"; try { var flIni = new FLDataFile(flIniPath, true); var dataPath = Path.GetFullPath(Path.Combine(flPath + Path.DirectorySeparatorChar + "EXE", flIni.GetSetting("Freelancer", "data path").Str(0))); LoadMBaseFile(new FLDataFile(dataPath + "\\missions\\mbases.ini", true), log); LoadNewsFile(new FLDataFile(dataPath + "\\missions\\news.ini", true), log); LoadGenericScripts(new FLDataFile(dataPath + "\\scripts\\gcs\\genericscripts.ini", true), log); } catch (Exception e) { log.AddLog(LogType.ERROR, "error: '" + e.Message + "' when parsing '" + flIniPath); } }
private void AddDockingPoint(string type, string hpname, float dockingRadius, Dictionary <string, HardpointData> hardpoints, ILogController log) { if (!hardpoints.ContainsKey(hpname)) { log.AddLog(LogType.ERROR, "error: hardpoint not found arch={0} hpname={1}", Nickname, hpname); return; } var dp = new DockingPoint { Type = (DockingPoint.DockingSphere) Enum.Parse(typeof(DockingPoint.DockingSphere), type.ToUpperInvariant()), HpName = hpname, DockingRadius = dockingRadius, Position = hardpoints[hpname].Position, Rotation = hardpoints[hpname].Rotation }; DockingPoints.Add(dp); }
public static void Load(string flPath, ILogController log) { // Load the universe and systems and all other static data string flIniPath = flPath + Path.DirectorySeparatorChar + "EXE" + Path.DirectorySeparatorChar + "Freelancer.ini"; try { var flIni = new FLDataFile(flIniPath, true); string dataPath = Path.GetFullPath(Path.Combine(flPath + Path.DirectorySeparatorChar + "EXE", flIni.GetSetting("Freelancer", "data path").Str(0))); log.AddLog(LogType.GENERAL, "Loading loadouts"); foreach (var entry in flIni.GetSettings("Data", "loadouts")) { LoadLoadout(dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } log.AddLog(LogType.GENERAL, "Loading factions"); foreach (var entry in flIni.GetSettings("Data", "groups")) { LoadFactions(dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } log.AddLog(LogType.GENERAL, "Loading universe"); foreach (var entry in flIni.GetSettings("Data", "universe")) { LoadUniverse(dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } log.AddLog(LogType.GENERAL, "Loading goods"); foreach (var entry in flIni.GetSettings("Data", "goods")) { LoadGoodData(dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } log.AddLog(LogType.GENERAL, "Loading markets"); foreach (var entry in flIni.GetSettings("Data", "markets")) { LoadBaseMarketData(dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } } catch (Exception e) { log.AddLog(LogType.ERROR, "error: '" + e.Message + "' when parsing '" + flIniPath); } }
private static void Load(string datapath, string path, ILogController log) { var ini = new FLDataFile(path, true); // Load elements by dependency order; slower, but avoids issues with file ordering // Second order dependencies: motors foreach (FLDataFile.Section sec in ini.Sections) { try { string sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName == "motor") { AddArchetype(new MotorArchetype(datapath, sec, log)); } } catch (Exception ex) { log.AddLog(LogType.ERROR, "ArchetypeDB load error: \"" + ex.Message + "\", item skipped"); } } // First order dependencies: munitions foreach (var sec in ini.Sections) { try { var sectionName = sec.SectionName.ToLowerInvariant(); switch (sectionName) { case "munition": AddArchetype(new MunitionArchetype(datapath, sec, log)); break; case "mine": AddArchetype(new MineArchetype(datapath, sec, log)); break; case "countermeasure": AddArchetype(new CounterMeasureArchetype(datapath, sec, log)); break; } } catch (Exception ex) { log.AddLog(LogType.ERROR, "ArchetypeDB load error: \"" + ex.Message + "\", item skipped"); } } // Zeroth order dependencies: everything else foreach (var sec in ini.Sections) { try { var sectionName = sec.SectionName.ToLowerInvariant(); switch (sectionName) { case "ship": AddArchetype(new ShipArchetype(datapath, sec, log)); break; case "gun": AddArchetype(new GunArchetype(datapath, sec, log)); break; case "countermeasuredropper": AddArchetype(new CounterMeasureDropperArchetype(datapath, sec, log)); break; case "minedropper": AddArchetype(new MineDropperArchetype(datapath, sec, log)); break; case "shieldgenerator": AddArchetype(new ShieldGeneratorArchetype(datapath, sec, log)); break; case "power": AddArchetype(new PowerArchetype(datapath, sec, log)); break; case "armor": AddArchetype(new ArmorArchetype(datapath, sec, log)); break; case "thruster": AddArchetype(new ThrusterArchetype(datapath, sec, log)); break; case "engine": AddArchetype(new EngineArchetype(datapath, sec, log)); break; case "repairkit": AddArchetype(new RepairKitArchetype(datapath, sec, log)); break; case "shieldbattery": AddArchetype(new ShieldBatteryArchetype(datapath, sec, log)); break; case "scanner": AddArchetype(new ScannerArchetype(datapath, sec, log)); break; case "simple": case "cloakingdevice": case "tractor": case "shield": case "attachedfx": case "internalfx": case "tradelane": case "commodity": case "lootcrate": case "cargopod": case "light": case "dynamicasteroid": case "asteroidmine": case "asteroid": case "solar": AddArchetype(new Archetype(datapath, sec, log)); break; } } catch (Exception ex) { log.AddLog(LogType.ERROR, "ArchetypeDB load error: \"" + ex.Message + "\", item skipped"); } } }
private static void LoadNewsFile(FLDataFile ini, ILogController log) { foreach (FLDataFile.Section sec in ini.Sections) { string section_name = sec.SectionName.ToLowerInvariant(); if (section_name == "newsitem") { var item = new NewsItem { Category = sec.GetSetting("category").UInt(0), Headline = sec.GetSetting("headline").UInt(0) }; var icon = sec.GetSetting("icon").Str(0); switch (icon) { case "critical": item.Icon = 1; break; case "world": item.Icon = 2; break; case "mission": item.Icon = 3; break; case "system": item.Icon = 4; break; case "faction": item.Icon = 5; break; case "universe": item.Icon = 6; break; default: item.Icon = 0; break; } item.Text = sec.GetSetting("text").UInt(0); item.Audio = sec.SettingExists("audio"); item.Logo = sec.GetSetting("logo").Str(0); foreach (var set in sec.Settings) { if (set.SettingName != "base") { continue; } var basename = set.Str(0); var bd = UniverseDB.FindBase(basename); if (bd == null) { log.AddLog(LogType.ERROR, "basename in news item not found, category={0} base={1}", item.Category, basename); } else { bd.News.Add(item); } } } } }
/// <summary> /// </summary> /// <param name="ini"></param> /// <param name="log"></param> /// First value is "minimum" mission difficulty. Second value is "maximum" mission difficulty. /// The server uses a funky formula for that, but preferably (and I've requested a hack applied /// to FLServer) it should just use those bare values. Third value is only used in the faction /// headers, and is the percentage chance for a mission for that faction to appear - the total /// of all mission lines of all factions for any given base should be 100. /// The difficulty then determines the payout and the various ships and solars that may appear. /// - Get as close to the max value of the difficulty by adding ships and solars in the waves /// in that mission, using the NPCRankToDiff.ini and MissionSolars.ini file for guidance /// - Then calculate the actual difficulty of the mission, and use the Diff2Money.ini file /// for calculating the mission payout. Then post the mission. private static void LoadMBaseFile(FLDataFile ini, ILogController log) { BaseData bd = null; foreach (var sec in ini.Sections) { string sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName == "mbase") { string nickname = sec.GetSetting("nickname").Str(0); bd = UniverseDB.FindBase(nickname); } else if (sectionName == "mvendor") { } else if (sectionName == "basefaction") { var bf = new BaseFaction { Faction = UniverseDB.FindFaction(sec.GetSetting("faction").Str(0)), Weight = sec.GetSetting("weight").Float(0), OffersMissions = sec.SettingExists("offers_missions") }; foreach (FLDataFile.Setting set in sec.Settings) { if (set.SettingName == "mission_type") { string mission_type = set.Str(0); } else if (set.SettingName == "npc") { string npcname = set.Str(0); } } } else if (sectionName == "gf_npc") { var bc = new BaseCharacter { Nickname = sec.GetSetting("nickname").Str(0).ToLowerInvariant() }; if (sec.SettingExists("base_appr")) { // fixme // var body; // var head; // var lefthead; // var righthand; } else { bc.Body = Utilities.CreateID(sec.GetSetting("body").Str(0)); bc.Head = Utilities.CreateID(sec.GetSetting("head").Str(0)); bc.Lefthand = Utilities.CreateID(sec.GetSetting("lefthand").Str(0)); bc.Righthand = Utilities.CreateID(sec.GetSetting("righthand").Str(0)); } bc.IndividualName = sec.GetSetting("individual_name").UInt(0); bc.Faction = UniverseDB.FindFaction(sec.GetSetting("affiliation").Str(0)); bc.Voice = Utilities.CreateID(sec.GetSetting("voice").Str(0)); if (sec.SettingExists("room")) { bc.Room = String.Format("{0:x}_{1}", bd.BaseID, sec.GetSetting("room").Str(0)); bc.RoomID = Utilities.CreateID(bc.Room); } foreach (var set in sec.Settings) { if (set.SettingName == "bribe") { var bb = new BaseBribe { Faction = UniverseDB.FindFaction(set.Str(0)), Cost = set.UInt(1), Text = set.UInt(2) }; bc.Bribes.Add(bb); } else if (set.SettingName == "rumor") { var br = new BaseRumor { Text = set.UInt(3) }; bc.Rumors.Add(br); } } if (bd != null) { bd.Chars[bc.Nickname] = bc; } else { log.AddLog(LogType.ERROR, "Character {0} can't find base", bc.Nickname); } } else if (sectionName == "mroom") { string nickname = sec.GetSetting("nickname").Str(0).ToLowerInvariant(); if (sec.SettingExists("character_density")) { bd.Rooms[nickname].CharacterDensity = sec.GetSetting("character_density").UInt(0); } foreach (FLDataFile.Setting set in sec.Settings) { if (set.SettingName == "fixture") { string name = set.Str(0).ToLowerInvariant(); string roomLocation = set.Str(1); string fidget_script = set.Str(2); string type = set.Str(3).ToLowerInvariant(); if (!bd.Chars.ContainsKey(name)) { log.AddLog(LogType.ERROR, "character not found at {0}", set.Desc); continue; } bd.Chars[name].Room = String.Format("{0:x}_{1}", bd.BaseID, nickname); bd.Chars[name].RoomID = Utilities.CreateID(bd.Chars[name].Room); bd.Chars[name].RoomLocation = roomLocation; bd.Chars[name].FidgetScript = fidget_script; bd.Chars[name].Type = type; } } } } }
/// <summary> /// Load the architecture database. /// </summary> /// <param name="flPath"></param> /// <param name="log"></param> public static void Load(string flPath, ILogController log) { log.AddLog(LogType.GENERAL, "Loading archetypes"); // Load the universe and systems and all other static data string flIniPath = flPath + Path.DirectorySeparatorChar + "EXE" + Path.DirectorySeparatorChar + "Freelancer.ini"; try { var flIni = new FLDataFile(flIniPath, false); string dataPath = Path.GetFullPath(Path.Combine(flPath + Path.DirectorySeparatorChar + "EXE", flIni.GetSetting("Freelancer", "data path").Str(0))); foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "WeaponModDB")) { LoadWeaponModDB(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "solar")) { Load(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "debris")) { Load(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "asteroids")) { Load(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "equipment")) { Load(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } foreach (FLDataFile.Setting entry in flIni.GetSettings("Data", "ships")) { Load(dataPath, dataPath + Path.DirectorySeparatorChar + entry.Str(0), log); } } catch (Exception e) { log.AddLog(LogType.ERROR, "Error '" + e.Message + "' when parsing '" + flIniPath); } //Compare our archetype db to flserver's db generated by adoxa's packetdump. //List<uint> validids = new List<uint>(); //byte[] x = File.ReadAllBytes(Properties.Settings.Default.FLPath + "\\EXE\\largeid.dat"); //for (int i=0; i<x.Length; i+=4) //{ // uint id = BitConverter.ToUInt32(x, i); // validids.Add(id); //} //for (int i = 0; i < validids.Count; i++) //{ // if (validids[i] != archetypes_by_small_id[i].archetypeid) // { // Archetype a1 = Find(validids[i]); // Archetype a2 = archetypes_by_small_id[i]; // log.AddLog(String.Format("short id mismatch index={0} validid={1} {2} ourid={3} {4}", i, validids[i], a1.nickname, // a2.archetypeid, a2.nickname)); // } //} //log.AddLog(String.Format("Archetypes loaded, large_count={0} small_count={1} valid_ids={2}", archetypes_by_large_id.Count, archetypes_by_small_id.Count, validids.Count)); }
public Archetype(string datapath, FLDataFile.Section sec, ILogController log) { Nickname = sec.GetSetting("nickname").Str(0); ArchetypeID = FLUtility.CreateID(Nickname); // Load the hardpoints and animation data for the model Hardpoints = new Dictionary <string, HardpointData>(); if (sec.SettingExists("loot_appearance")) { LootAppearance = sec.GetSetting("loot_appearance").Str(0); } if (sec.SettingExists("pod_appearance")) { PodAppearance = sec.GetSetting("loot_appearance").Str(0); } if (sec.SettingExists("DA_archetype")) { ModelPath = datapath + Path.DirectorySeparatorChar + sec.GetSetting("DA_archetype").Str(0); var utf = new UTFFile(); TreeNode root = utf.LoadUTFFile(ModelPath); CmpFixData fix; CmpRevData rev; CmpPrisData pris; CmpSphereData sphere; var file_to_object = new Dictionary <string, string>(); var cons_parent = new Dictionary <string, string>(); var cons_position = new Dictionary <string, Vector>(); var cons_orientation = new Dictionary <string, Matrix>(); if (root.Nodes.ContainsKey("Cmpnd")) { TreeNode cmpnd = root.Nodes["Cmpnd"]; foreach (TreeNode n in cmpnd.Nodes) { if (n.Name == "Cons") { TreeNode cons = n; if (cons.Nodes.ContainsKey("Fix")) { TreeNode fixNode = cons.Nodes["Fix"]; try { fix = new CmpFixData(fixNode.Tag as byte[]); foreach (CmpFixData.Part p in fix.Parts) { cons_parent[p.ChildName] = p.ParentName; cons_position[p.ChildName] = new Vector(p.OriginX, p.OriginY, p.OriginZ); var m = new Matrix { M00 = p.RotMatXX, M01 = p.RotMatXY, M02 = p.RotMatXZ, M10 = p.RotMatYX, M11 = p.RotMatYY, M12 = p.RotMatYZ, M20 = p.RotMatZX, M21 = p.RotMatZY, M22 = p.RotMatZZ }; cons_orientation[p.ChildName] = m; } } catch (Exception) { fix = null; } } if (cons.Nodes.ContainsKey("Pris")) { TreeNode prisNode = cons.Nodes["Pris"]; try { pris = new CmpPrisData(prisNode.Tag as byte[]); foreach (CmpPrisData.Part p in pris.Parts) { cons_parent[p.ChildName] = p.ParentName; cons_position[p.ChildName] = new Vector(p.OriginX, p.OriginY, p.OriginZ); var m = new Matrix { M00 = p.RotMatXX, M01 = p.RotMatXY, M02 = p.RotMatXZ, M10 = p.RotMatYX, M11 = p.RotMatYY, M12 = p.RotMatYZ, M20 = p.RotMatZX, M21 = p.RotMatZY, M22 = p.RotMatZZ }; cons_orientation[p.ChildName] = m; } } catch (Exception) { pris = null; } } if (cons.Nodes.ContainsKey("Rev")) { TreeNode revNode = cons.Nodes["Rev"]; try { rev = new CmpRevData(revNode.Tag as byte[]); foreach (CmpRevData.Part p in rev.Parts) { cons_parent[p.ChildName] = p.ParentName; cons_position[p.ChildName] = new Vector(p.OriginX, p.OriginY, p.OriginZ); var m = new Matrix { M00 = p.RotMatXX, M01 = p.RotMatXY, M02 = p.RotMatXZ, M10 = p.RotMatYX, M11 = p.RotMatYY, M12 = p.RotMatYZ, M20 = p.RotMatZX, M21 = p.RotMatZY, M22 = p.RotMatZZ }; cons_orientation[p.ChildName] = m; } } catch (Exception) { rev = null; } } if (cons.Nodes.ContainsKey("Sphere")) { TreeNode sphereNode = cons.Nodes["Sphere"]; try { sphere = new CmpSphereData(sphereNode.Tag as byte[]); foreach (CmpSphereData.Part p in sphere.Parts) { cons_parent[p.ChildName] = p.ParentName; cons_position[p.ChildName] = new Vector(p.OriginX, p.OriginY, p.OriginZ); var m = new Matrix { M00 = p.RotMatXX, M01 = p.RotMatXY, M02 = p.RotMatXZ, M10 = p.RotMatYX, M11 = p.RotMatYY, M12 = p.RotMatYZ, M20 = p.RotMatZX, M21 = p.RotMatZY, M22 = p.RotMatZZ }; cons_orientation[p.ChildName] = m; } } catch (Exception) { sphere = null; } } } else { if (n.Nodes.ContainsKey("Object name") && n.Nodes.ContainsKey("File name")) { file_to_object.Add(Utilities.GetString(n.Nodes["File name"]), Utilities.GetString(n.Nodes["Object name"])); } } } } foreach (TreeNode hp in utf.Hardpoints.Nodes) { TreeNode hpnode = null, parentnode = null; TreeNode[] matches = root.Nodes.Find(hp.Name, true); foreach (TreeNode m in matches) { hpnode = m; parentnode = hpnode.Parent.Parent.Parent; if (file_to_object.ContainsKey(parentnode.Name)) { break; } } if (hpnode == null) { continue; } var hpd = new HardpointData(hpnode); if (parentnode != root) { string consName = file_to_object[parentnode.Name]; var positionOffset = new Vector(); var rotationOffset = new Matrix(); while (consName.ToLowerInvariant() != "root") { positionOffset += cons_position[consName]; rotationOffset *= cons_orientation[consName]; consName = cons_parent[consName]; } hpd.Position += positionOffset; hpd.Rotation *= rotationOffset; } Hardpoints[hpd.Name.ToLowerInvariant()] = hpd; if (hpd.Name.ToLowerInvariant() == "hpmount") { HpMount = hpd; } } } if (ModelPath != null) { string surPath = Path.ChangeExtension(ModelPath, ".sur"); if (File.Exists(surPath)) { try { new SurFile(surPath); } catch { log.AddLog(LogType.ERROR, "sur load failed for " + surPath); } } } if (sec.SettingExists("mass")) { Mass = sec.GetSetting("mass").Float(0); } if (sec.SettingExists("hit_pts")) { HitPts = sec.GetSetting("hit_pts").Float(0); } if (sec.SettingExists("type")) { try { Type = (ObjectType)Enum.Parse(typeof(ObjectType), sec.GetSetting("type").Str(0).ToUpperInvariant()); } catch (Exception) { Type = ObjectType.NONE; } } // Load the docking points for the model foreach (FLDataFile.Setting set in sec.Settings) { if (set.SettingName == "docking_sphere") { string moortype = set.Str(0).ToLowerInvariant(); string hpname = set.Str(1).ToLowerInvariant(); float dockingRadius = set.Float(2); AddDockingPoint(moortype, hpname, dockingRadius, Hardpoints, log); } } if (sec.SettingExists("loadout")) { Loadout = sec.GetSetting("loadout").Str(0); } // If this is a tradelane ring, load the docking points for it if (Type == ObjectType.TRADELANE_RING) { AddDockingPoint("tradelane_ring", "hpleftlane", 0, Hardpoints, log); AddDockingPoint("tradelane_ring", "hprightlane", 0, Hardpoints, log); } // FIXME: Load or build and surface used for collision detection for this model. }
/// <summary> /// Load a single system /// </summary> /// <param name="path"></param> /// <param name="system"></param> /// <param name="log"></param> private static void LoadSystem(string path, StarSystem system, ILogController log) { try { var ini = new FLDataFile(path, true); foreach (FLDataFile.Section sec in ini.Sections) { string sectionName = sec.SectionName.ToLowerInvariant(); if (sectionName == "zone") { var zone = new Zone { shape = null, nickname = sec.GetSetting("nickname").Str(0) }; zone.zoneid = FLUtility.CreateID(zone.nickname); Vector position = sec.GetSetting("pos").Vector(); var orientation = new Matrix(); double[] size = null; string shape = sec.GetSetting("shape").Str(0).ToLowerInvariant(); foreach (FLDataFile.Setting set in sec.Settings) { string settingName = set.SettingName.ToLowerInvariant(); switch (settingName) { case "rotation": orientation = Matrix.EulerDegToMatrix(set.Vector()); break; case "size": size = new double[set.NumValues()]; for (int a = 0; a < size.Length; a++) { size[a] = set.Float(a); } break; case "damage": zone.damage = set.Float(0); break; case "interference": zone.interference = set.Float(0); break; case "encounter": break; case "faction": break; case "density": zone.density = set.Float(0); break; } } if (size != null) { if (shape == "sphere" && size.Length == 1) { zone.shape = new Sphere(position, orientation, size[0]); } else if (shape == "cylinder" && size.Length == 2) { zone.shape = new Cylinder(position, orientation, size[0], size[1]); } else if (shape == "ellipsoid" && size.Length == 3) { zone.shape = new Ellipsoid(position, orientation, new Vector(size[0], size[1], size[2])); } else if (shape == "box" && size.Length == 3) { zone.shape = new Box(position, orientation, new Vector(size[0], size[1], size[2])); } else if (shape == "ring" && size.Length == 3) { zone.shape = new Ring(position, orientation, size[0], size[1], size[2]); } } system.Zones.Add(zone); } else if (sectionName == "object") { var solar = new Object.Solar.Solar(system, sec.GetSetting("nickname").Str(0)); if (sec.SettingExists("pos")) { solar.Position = sec.GetSetting("pos").Vector(); } if (sec.SettingExists("rotate")) { Vector euler = sec.GetSetting("rotate").Vector(); solar.Orientation = Matrix.EulerDegToMatrix(euler); } if (sec.SettingExists("base")) { // When a ship undocks, it undocks from the solar specified by baseid. // uint baseid = FLUtility.CreateID(sec.GetSetting("base").Str(0)); // FIXME: check base exists // solar.base_data = bases[baseid]; // bases[baseid].solar = solar; } if (sec.SettingExists("archetype")) { uint archetypeid = FLUtility.CreateID(sec.GetSetting("archetype").Str(0)); solar.Arch = ArchetypeDB.Find(archetypeid); solar.GetLoadout(); // FIXME: check archetype exists } if (sec.SettingExists("dock_with")) { uint baseid = FLUtility.CreateID(sec.GetSetting("dock_with").Str(0)); solar.BaseData = Bases[baseid]; } if (sec.SettingExists("goto")) { solar.DestinationObjid = FLUtility.CreateID(sec.GetSetting("goto").Str(1)); solar.DestinationSystemid = FLUtility.CreateID(sec.GetSetting("goto").Str(0)); } if (sec.SettingExists("prev_ring")) { solar.PrevRing = FLUtility.CreateID(sec.GetSetting("prev_ring").Str(0)); } if (sec.SettingExists("next_ring")) { solar.NextRing = FLUtility.CreateID(sec.GetSetting("next_ring").Str(0)); } if (sec.SettingExists("reputation")) { Faction faction = FindFaction(sec.GetSetting("reputation").Str(0)); if (faction == null) { log.AddLog(LogType.ERROR, "error: not valid faction={0}", sec.GetSetting("reputation").Str(0)); } else { solar.Faction = faction; } } // Rebuild the docking points from the archetype // to the solar position and rotation. foreach (DockingPoint dockingPoint in solar.Arch.DockingPoints) { var dockingObj = new DockingObject { Type = dockingPoint.Type, Solar = solar, Index = (uint)solar.Arch.DockingPoints.IndexOf(dockingPoint), DockingRadius = dockingPoint.DockingRadius, Position = solar.Orientation * dockingPoint.Position }; // rotate the hardpoint by the base orientation and then dockingObj.Position += solar.Position; // the ship launch rotation is the base rotation rotated by the hardpoint rotation dockingObj.Rotation = dockingPoint.Rotation * solar.Orientation; if (solar.BaseData != null) { solar.BaseData.LaunchObjs.Add(dockingObj); } solar.DockingObjs.Add(dockingObj); } // Store the solar. system.Solars[solar.Objid] = solar; Solars[solar.Objid] = solar; if (solar.Arch.Type == Archetype.ObjectType.JUMP_GATE || solar.Arch.Type == Archetype.ObjectType.JUMP_HOLE) { system.Gates.Add(solar); } } } } catch (Exception e) { log.AddLog(LogType.ERROR, "error: '" + e.Message + "' when parsing '" + path); if (e.InnerException != null) { log.AddLog(LogType.ERROR, "error: '" + e.InnerException.Message + "' when parsing '" + path); } } }
/// <summary> /// Load the specified character file, resetting all character specific /// content for this player and notifying all players of the name. /// </summary> /// <param name="account">Player account</param> /// <param name="log"></param> /// <returns>Returns null on successful load otherwise returns error message as a string.</returns> public string LoadCharFile(Account account, ILogController log) { if (account == null) { log.AddLog(LogType.ERROR, "Broken account found!"); return("Account is null!"); } //null checks made earlier // ReSharper disable once PossibleInvalidOperationException PlayerAccount = account; Wgrp = new WeaponGroup(); // ReSharper disable once PossibleNullReferenceException Name = PlayerAccount.CharName; Money = PlayerAccount.Money; var arch = ArchetypeDB.Find(PlayerAccount.Ship); if (arch is ShipArchetype) { Ship.Arch = arch; } else { return("invalid ship"); } if (ShipState.RepGroup == "") { Ship.faction = new Faction(); } else { Ship.faction = UniverseDB.FindFaction(ShipState.RepGroup); if (Ship.faction == null) { return("invalid faction"); } } Ship.System = UniverseDB.FindSystem(PlayerAccount.System); if (Ship.System == null) { return("invalid system"); } if (ShipState.Base == null) { Ship.Basedata = null; } else { Ship.Basedata = UniverseDB.FindBase(ShipState.Base); if (Ship.Basedata == null) { return("invalid base"); } } if (ShipState.LastBase == "") { Ship.RespawnBasedata = null; return("no respawn base"); } Ship.RespawnBasedata = UniverseDB.FindBase(ShipState.LastBase); if (Ship.RespawnBasedata == null) { return("invalid respawn base"); } if (Ship.Basedata == null) { if (ShipState.Position != null) { Ship.Position = ShipState.Position; } if (ShipState.Rotate != null) { Ship.Orientation = Matrix.EulerDegToMatrix(ShipState.Rotate); } } //TODO: why ShipState.Hull is always true Ship.Health = ShipState.Hull; if (Ship.Health <= 0) { Ship.Health = 0.05f; } Ship.voiceid = FLUtility.CreateID(Appearance.Voice); //TODO: calculate rank // ReSharper disable once PossibleNullReferenceException Ship.Rank = PlayerAccount.Rank; Ship.com_body = Appearance.Body; Ship.com_head = Appearance.Head; Ship.com_lefthand = Appearance.LeftHand; Ship.com_righthand = Appearance.RightHand; Ship.Items.Clear(); uint hpid = 34; foreach (var set in Equipment) { var si = new ShipItem { arch = ArchetypeDB.Find(set.Arch), hpname = set.HpName, health = set.Health, count = 1, mission = false, mounted = true, hpid = hpid++ }; Ship.Items[si.hpid] = si; } foreach (var set in Cargo) { var si = new ShipItem { arch = ArchetypeDB.Find(set.Arch), hpname = "", count = set.Count, health = 1.0f, mission = false, mounted = false, hpid = hpid++ }; Ship.Items[si.hpid] = si; } Ship.Reps.Clear(); foreach (var set in RepDictionary) { float rep = set.Value; var faction = UniverseDB.FindFaction(set.Key); if (faction == null) { // ReSharper disable once PossibleNullReferenceException log.AddLog(LogType.ERROR, "error: faction not found char={0} faction={1}", account.CharName, set.Value); } else { Ship.Reps[faction] = rep; } } //Visits.Clear(); //foreach (var set in Visits) //{ // Visits[set.Key] = set.Value; //} Ship.CurrentAction = null; return(null); }
/** * This function is called when a connection from a freelancer player is made to the server. */ public void dps_PlayerCreated(Session session) { _log.AddLog(LogType.GENERAL, "Player created name={0}", session.Client); AddEvent(new DPSessionConnectedEvent(session)); }
/// <summary> /// Send a message to the dplay stack for transmission to a freelancer client. /// Compress the message if it's longer than 80 bytes or so. /// </summary> /// <param name="dplayid"></param> /// <param name="msg"></param> public void SendMessage(Session session, byte[] msg) { if (msg[0] != 0xFF) { _log.AddLog(LogType.FL_MSG, "s>c client={0} tx={1}", session.Client, msg); } if (msg.Length > 0x50) { using (var ms = new MemoryStream()) { using (var zs = new ZlibStream(ms, CompressionMode.Compress)) zs.Write(msg, 0, msg.Length); msg = ms.ToArray(); } } _dplay.SendTo(session, msg); }
public void ProcessPktFromClient(byte[] pkt, IPEndPoint client) { _log.AddLog(LogType.DPLAY_MSG, "c>s client={0} pkt={1}", client, pkt); // If this message is too short, chuck it away if (pkt.Length < 2) { return; } // If this message is a enum server status then reply to the query. This tricks people // into thinking the server has a better ping than it does. int pos = 0; uint cmd = FLMsgType.GetUInt8(pkt, ref pos); if (cmd == 0x00 && pkt.Length >= 4) { uint opcode = FLMsgType.GetUInt8(pkt, ref pos); if (opcode == 0x02 && pkt.Length >= 4) { uint enum_payload = FLMsgType.GetUInt16(pkt, ref pos); SendCmdEnumResponse(client, (ushort)enum_payload); } } // If the data is at least 12 bytes and the first byte is // either 0x80 or 0x88 (PACKET_COMMAND_CFRAME or PACKET_COMMAND_CFRAME | // PACKET_COMMAND_POLL), it MUST process the message as a CFRAME // (section 3.1.5.1) command frame. else if ((cmd == 0x80 || cmd == 0x88) && pkt.Length >= 12) { uint opcode = FLMsgType.GetUInt8(pkt, ref pos); // The CONNECT packet is used to request a connection. If accepted, the response // is a CONNECTED (section 2.2.1.2) packet if (opcode == 0x01) { byte msg_id = FLMsgType.GetUInt8(pkt, ref pos); byte rsp_id = FLMsgType.GetUInt8(pkt, ref pos); uint version = FLMsgType.GetUInt32(pkt, ref pos); uint dplayid = FLMsgType.GetUInt32(pkt, ref pos); uint timestamp = FLMsgType.GetUInt32(pkt, ref pos); // Create a new session. Session sess = GetSession(client); if (sess == null) { sess = new Session(client); sess.DPlayID = dplayid; lock (_dplaySessions) { _dplaySessions[client] = sess; } } lock (sess) { // If the session id has changed, assume that the server is wrong // and kill the existing connection and start a new one. // This behaviour differs from the dplay specification. if (sess.DPlayID != 0 && sess.DPlayID != dplayid) { Destroy(sess, "changed dsessid"); } // If the session is fully connected because the client has // sent us a connect acknowledge then ignore this. if (sess.SessionState == Session.State.CONNECTED) { return; } // Otherwise this is a new connection. Reset the session information. sess.SessionState = Session.State.CONNECTING; sess.LastClientRxTime = DateTime.UtcNow; sess.StartTime = DateTime.Now; sess.Rtt = 200; sess.LostRx = 0; sess.BytesRx = 0; sess.LostTx = 0; sess.BytesTx = 0; sess.NextRxSeq = 0; sess.NextTxSeq = 0; sess.MsgID = 0; sess.OutOfOrder.Clear(); sess.UserData.Clear(); sess.UserDataPendingAck.Clear(); sess.MultipleDframePacket = false; sess.SessionTimer = new Timer(SessionTimer, sess, 100, 20); SendCmdConnectAccept(sess, msg_id); } } // Receive a SACK and process it else if (opcode == 0x06) { byte flags = FLMsgType.GetUInt8(pkt, ref pos); byte retry = FLMsgType.GetUInt8(pkt, ref pos); // The seq field indicates the seq of the next message that the client will send. byte seq = FLMsgType.GetUInt8(pkt, ref pos); // The next_rx field indicates the message seq that the client is waiting to receive byte nrcv = FLMsgType.GetUInt8(pkt, ref pos); pos += 2; // skip padding uint timestamp = FLMsgType.GetUInt32(pkt, ref pos); // Ignore packets for sessions that don't exist Session sess = GetSession(client); if (sess == null) { return; } lock (sess) { sess.LastClientRxTime = DateTime.UtcNow; sess.BytesRx += pkt.Length; // If the hi sack mask is present, resend any requested packets. if ((flags & 0x02) == 0x02) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, nrcv); } // If the hi sack mask is present, resend any requested packets. if ((flags & 0x04) == 0x04) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, (byte)(nrcv + 32)); } // At this point bSeq sequence ID is valid, the bNRcv field // is to be inspected. All previously sent TRANS_USERDATA_HEADER packets that // are covered by the bNRcv sequence ID, that is, those packets that had been sent // with bSeq values less than bNRcv (accounting for 8-bit counter wrapping) are // acknowledged. These packets do not have to be remembered any longer, and their // retry timers can be canceled. DoAcknowledgeUserData(sess, nrcv); // Try to send data if there's data waiting to be sent and send a // selective acknowledgement if we didn't sent a dframe and the client // requested an acknowledgement. if (!SendDFrame(sess) && cmd == 0x88) { SendCmdSACK(sess); } } } } // If a packet arrives, the recipient SHOULD first check whether // it is large enough to be a minimal data frame (DFRAME) (4 bytes) // and whether the first byte has the low bit (PACKET_COMMAND_DATA) set. else if ((cmd & 0x01) == 0x01 && pkt.Length >= 4) { uint control = FLMsgType.GetUInt8(pkt, ref pos); byte seq = FLMsgType.GetUInt8(pkt, ref pos); byte nrcv = FLMsgType.GetUInt8(pkt, ref pos); // Ignore packets for sessions that don't exist Session sess = GetSession(client); if (sess == null) { return; } lock (sess) { sess.LastClientRxTime = DateTime.UtcNow; sess.BytesRx += pkt.Length; // This is a disconnect. We ignore the soft disconnect and immediately // drop the session repeating the disconnect a few times to improve the // probability of it getting through. if ((control & 0x08) == 0x08) { Destroy(sess, "client request"); return; } // TRANS_USERDATA_HEADER bSeq field MUST be either the next sequence // ID expected or within 63 packets beyond the ID expected by the receiver. // If the sequence ID is not within this range, the payload MUST be ignored. // In addition, a SACK packet SHOULD be sent indicating the expected sequence ID. if (!InWindow(seq, sess.NextRxSeq)) { SendCmdSACK(sess); return; } // If the sequence ID is out of order, but still within 63 packets, // the receiver SHOULD queue the payload until it receives either: // - A delayed or retried transmission of the missing packet or packets, // and can now process the sequence in order. // - A subsequent packet with a send mask indicating that the missing // packet or packets did not use PACKET_COMMAND_RELIABLE and will never // be retried. Therefore, the receiver should advance its sequence as if // it had already received and processed the packets. if (seq != sess.NextRxSeq) { _log.AddLog(LogType.DPLAY_MSG, "c>s out of order pkt received client={0} queuing seq={1:X} next_rx_seq={2:X}", sess.Client, seq, sess.NextRxSeq); sess.OutOfOrder[seq] = pkt; SendCmdSACK(sess); return; } //Test code to simulate packet loss //if (rand.Next(5) == 1) //{ // log.AddLog(String.Format("c>s: DROPPING THE PACKET NOW {0:X}", seq)); // return; //} // Note if this was a retried dframe. if ((control & 0x01) == 0x01) { sess.LostRx++; } // When one or both of the optional SACK mask 32-bit fields is present, and one // or more bits are set in the fields, the sender is indicating that it received a // packet or packets out of order, presumably due to packet loss. The two 32-bit, // little-endian fields MUST be considered as one 64-bit field, where dwSACKMask1 // is the low 32 bits and dwSACKMask2 is the high 32 bits. If either 32-bit field // is not available, the entire contents of the 64-bit field MUST be considered as all 0. // The receiver of a SACK mask SHOULD loop through each bit of the combined 64-bit value // in the ascending order of significance. Each bit corresponds to a sequence ID after // bNRcv. If the bit is set, it indicates that the corresponding packet was received // out of order. // The receiver of a SACK mask SHOULD shorten the retry timer for the first frame of // the window to speed recovery from the packet loss. The recommended duration is // 10 milliseconds. This value can be modified according to application and network // requirements. The receiver MAY also choose to remove the selectively acknowledged // packets from its list to retry. if ((control & 0x10) == 0x10) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, nrcv); } if ((control & 0x20) == 0x20) { uint mask = FLMsgType.GetUInt32(pkt, ref pos); DoRetryOnSACKMask(sess, mask, (byte)(nrcv + 32)); } // When one or both of the optional send mask 32-bit fields is present, and one or // more bits are set the fields, the sender is indicating that it sent a packet or // packets that were not marked as reliable and did not receive an acknowledgement yet. // The two 32-bit, little-endian fields MUST be considered as one 64-bit field, where // dwSendMask1 is the low 32 bits and dwSendMask2 is the high 32 bits. If either 32-bit // field is not available, the entire contents of the 64-bit field MUST be considered // as all 0. // The receiver of a send mask SHOULD loop through each bit of the combined 64-bit // value from the least significant bit to the most significant in little-endian byte // order. Each bit corresponds to a sequence ID prior to bSeq, and if that is the bit // that is set, it indicates that the corresponding packet was not sent reliably and // will not be retried. If the recipient of the send mask had not received the packet // and had not already processed a send mask that identified the sequence ID, it SHOULD // consider the packet as dropped and release its placeholder in the sequence. That is, // any sequential messages that could not be indicated because of the gap in the sequence // where the packet that was not marked as reliable had been SHOULD now be reported to // the upper layer. if ((control & 0x40) == 0x40) { FLMsgType.GetUInt32(pkt, ref pos); } if ((control & 0x80) == 0x80) { FLMsgType.GetUInt32(pkt, ref pos); } // However, freelancer always uses reliable packets and so ignore sendmasks. // At this point, we've received the packet we wanted to. Advance the sequence number count // and process this message. sess.NextRxSeq++; ProcessTransUserData(sess, pkt, pos); // If there are queued out of order packets, try to process these. while (sess.OutOfOrder.ContainsKey(sess.NextRxSeq)) { _log.AddLog(LogType.DPLAY_MSG, "c>s unqueuing out of order pkt client={0} seq={1:X}", sess.Client, sess.NextRxSeq); pkt = sess.OutOfOrder[sess.NextRxSeq]; sess.OutOfOrder.Remove(sess.NextRxSeq); sess.NextRxSeq++; ProcessTransUserData(sess, pkt, pos); // fixme: pos could be wrong if we received a sack mask } // At this point bSeq sequence ID is valid, the bNRcv field // is to be inspected. All previously sent TRANS_USERDATA_HEADER packets that // are covered by the bNRcv sequence ID, that is, those packets that had been sent // with bSeq values less than bNRcv (accounting for 8-bit counter wrapping) are // acknowledged. These packets do not have to be remembered any longer, and their // retry timers can be canceled. DoAcknowledgeUserData(sess, nrcv); // We always do an immediate acknowledge as bandwidth isn't a particular concern // but fast recovery from lost packets is. if (!SendDFrame(sess)) { SendCmdSACK(sess); } } } }