void BuildTree(MapFile map, IEnumerable <JmfGroup> groups, IReadOnlyCollection <JmfEntity> entities) { Dictionary <int, int> groupIds = new Dictionary <int, int>(); // file group id -> actual id Dictionary <int, MapObject> objTree = new Dictionary <int, MapObject>(); // object id -> object int currentId = 2; // worldspawn is 1 objTree[1] = map.Worldspawn; JmfEntity worldspawnEntity = entities.FirstOrDefault(x => x.Entity.ClassName == "worldspawn"); if (worldspawnEntity != null) { map.Worldspawn.Properties = worldspawnEntity.Entity.Properties; map.Worldspawn.Color = worldspawnEntity.Entity.Color; map.Worldspawn.SpawnFlags = worldspawnEntity.Entity.SpawnFlags; map.Worldspawn.Visgroups = worldspawnEntity.Entity.Visgroups; } // Jackhammer doesn't allow a group within an entity, so groups // will only be children of worldspawn or another group. We can // build the group hierarchy immediately. List <JmfGroup> groupList = groups.ToList(); int groupCount = groupList.Count; while (groupList.Any()) { List <JmfGroup> pcs = groupList.Where(x => x.ID == x.ParentID || x.ParentID == 0 || groupIds.ContainsKey(x.ParentID)).ToList(); foreach (JmfGroup g in pcs) { int gid = currentId++; groupIds[g.ID] = gid; groupList.Remove(g); Group group = new Group { Color = g.Color }; int parentObjId = g.ID == g.ParentID || g.ParentID == 0 ? 1 : groupIds[g.ParentID]; objTree[parentObjId].Children.Add(group); objTree[gid] = group; } if (groupList.Count == groupCount) { break; // no groups processed, can't continue } groupCount = groupList.Count; } // For non-worldspawn solids, they are direct children of their entity. // For non-worldspawn entities, they're either a child of a group or of the worldspawn. foreach (JmfEntity entity in entities.Where(x => x != worldspawnEntity)) { int parentId = groupIds.ContainsKey(entity.GroupID) ? groupIds[entity.GroupID] : 1; objTree[parentId].Children.Add(entity.Entity); // Put all the entity's solids straight underneath this entity entity.Entity.Children.AddRange(entity.Solids.Select(x => x.Solid)); } // For worldspawn solids, they're either a child of a group or of the worldspawn. if (worldspawnEntity != null) { foreach (JmfSolid solid in worldspawnEntity.Solids) { int parentId = groupIds.ContainsKey(solid.GroupID) ? groupIds[solid.GroupID] : 1; objTree[parentId].Children.Add(solid.Solid); } } }
List <JmfEntity> ReadEntities(MapFile map, BinaryReader br) { List <JmfEntity> entities = new List <JmfEntity>(); while (br.BaseStream.Position < br.BaseStream.Length) { JmfEntity ent = new JmfEntity { Entity = new Entity { ClassName = ReadString(br) } }; Vector3 origin = br.ReadVector3(); ent.Entity.Properties["origin"] = $"{origin.X} {origin.Y} {origin.Z}"; ent.Flags = br.ReadInt32(); ent.GroupID = br.ReadInt32(); br.ReadInt32(); // group id again ent.Entity.Color = br.ReadRGBAColour(); // useless (?) list of 13 strings for (int i = 0; i < 13; i++) { ReadString(br); } ent.Entity.SpawnFlags = br.ReadInt32(); br.ReadBytes(76); // unknown (!) int numProps = br.ReadInt32(); for (int i = 0; i < numProps; i++) { string key = ReadString(br); string value = ReadString(br); if (key != null && value != null) { ent.Entity.Properties[key] = value; } } ent.Entity.Visgroups = new List <int>(); int numVisgroups = br.ReadInt32(); for (int i = 0; i < numVisgroups; i++) { ent.Entity.Visgroups.Add(br.ReadInt32()); } int numSolids = br.ReadInt32(); for (int i = 0; i < numSolids; i++) { ent.Solids.Add(ReadSolid(map, br)); } entities.Add(ent); } return(entities); }