def read_ent_data(self) -> VMF: """Parse in entity data. This returns a VMF object, with entities mirroring that in the BSP. No brushes are read. """ ent_data = self.get_lump(BSP_LUMPS.ENTITIES) vmf = VMF() cur_ent = None # None when between brackets. seen_spawn = False # The first entity is worldspawn. # This code performs the same thing as property_parser, but simpler # since there's no nesting, comments, or whitespace, except between # key and value. We also operate directly on the (ASCII) binary. for line in ent_data.splitlines(): if line == b'{': if cur_ent is not None: raise ValueError( '2 levels of nesting after {} ents'.format( len(vmf.entities))) if not seen_spawn: cur_ent = vmf.spawn seen_spawn = True else: cur_ent = Entity(vmf) elif line == b'}': if cur_ent is None: raise ValueError( 'Too many closing brackets after {} ents'.format( len(vmf.entities))) if cur_ent is vmf.spawn: if cur_ent['classname'] != 'worldspawn': raise ValueError('No worldspawn entity!') else: # The spawn ent is stored in the attribute, not in the ent # list. vmf.add_ent(cur_ent) cur_ent = None elif line == b'\x00': # Null byte at end of lump. if cur_ent is not None: raise ValueError("Last entity didn't end!") return vmf else: # Line is of the form <"key" "val"> key, value = line.split(b'" "') decoded_key = key[1:].decode('ascii') decoded_val = value[:-1].decode('ascii') if 27 in value: # All outputs use the comma_sep, so we can ID them. cur_ent.add_out( Output.parse(Property(decoded_key, decoded_val))) else: # Normal keyvalue. cur_ent[decoded_key] = decoded_val # This keyvalue needs to be stored in the VMF object too. # The one in the entity is ignored. vmf.map_ver = conv_int(vmf.spawn['mapversion'], vmf.map_ver) return vmf
def read_ent_data(self) -> VMF: """Parse in entity data. This returns a VMF object, with entities mirroring that in the BSP. No brushes are read. """ ent_data = self.get_lump(BSP_LUMPS.ENTITIES) vmf = VMF() cur_ent = None # None when between brackets. seen_spawn = False # The first entity is worldspawn. # This code performs the same thing as property_parser, but simpler # since there's no nesting, comments, or whitespace, except between # key and value. We also operate directly on the (ASCII) binary. for line in ent_data.splitlines(): if line == b'{': if cur_ent is not None: raise ValueError( '2 levels of nesting after {} ents'.format( len(vmf.entities))) if not seen_spawn: cur_ent = vmf.spawn seen_spawn = True else: cur_ent = Entity(vmf) continue elif line == b'}': if cur_ent is None: raise ValueError(f'Too many closing brackets after' f' {len(vmf.entities)} ents!') if cur_ent is vmf.spawn: if cur_ent['classname'] != 'worldspawn': raise ValueError('No worldspawn entity!') else: # The spawn ent is stored in the attribute, not in the ent # list. vmf.add_ent(cur_ent) cur_ent = None continue elif line == b'\x00': # Null byte at end of lump. if cur_ent is not None: raise ValueError("Last entity didn't end!") return vmf if cur_ent is None: raise ValueError("Keyvalue outside brackets!") # Line is of the form <"key" "val"> key, value = line.split(b'" "') decoded_key = key[1:].decode('ascii') decoded_value = value[:-1].decode('ascii') # Now, we need to figure out if this is a keyvalue, # or connection. # If we're L4D+, this is easy - they use 0x1D as separator. # Before, it's a comma which is common in keyvalues. # Assume it's an output if it has exactly 4 commas, and the last two # successfully parse as numbers. if 27 in value: # All outputs use the comma_sep, so we can ID them. cur_ent.add_out( Output.parse(Property(decoded_key, decoded_value))) elif value.count(b',') == 4: try: cur_ent.add_out( Output.parse(Property(decoded_key, decoded_value))) except ValueError: cur_ent[decoded_key] = decoded_value else: # Normal keyvalue. cur_ent[decoded_key] = decoded_value # This keyvalue needs to be stored in the VMF object too. # The one in the entity is ignored. vmf.map_ver = conv_int(vmf.spawn['mapversion'], vmf.map_ver) return vmf