def edit_region_mesh_range(region: Region, new_mesh_range: Hex): new_mesh_range_str = str(new_mesh_range) assert new_mesh_range_str.startswith( '0x00000') # only last 3 digits may be used old_mesh_range: Hex = region.get_data().mesh_range if new_mesh_range == old_mesh_range: print('no change in mesh range') return print('edit mesh range: ' + str(old_mesh_range) + ' -> ' + str(new_mesh_range)) region.get_data().mesh_range = new_mesh_range region.save() new_mesh_prefix = new_mesh_range_str[7:] assert len(new_mesh_prefix) == 3, new_mesh_prefix # replace & collect in node_mesh_index node_mesh_index_file = region.gas_dir.get_subdir('index').get_gas_file( 'node_mesh_index') mesh_replacements = list() for node_mesh_attr in node_mesh_index_file.get_gas().get_section( 'node_mesh_index').items: assert isinstance(node_mesh_attr, Attribute) old_hex = Hex.parse(node_mesh_attr.name) new_hex = Hex.parse('0x' + new_mesh_prefix + node_mesh_attr.name[5:]) mesh_replacements.append((old_hex, new_hex)) node_mesh_attr.name = new_hex.to_str_lower() node_mesh_index_file.save() # replace mesh references in terrain nodes replace_hexes_in_file( region.gas_dir.get_subdir('terrain_nodes').get_gas_file('nodes').path, mesh_replacements)
def write_gas(self) -> Gas: node_sections = [Section('t:snode,n:' + snode.guid.to_str_lower(), [ Attribute('bounds_camera', snode.bounds_camera), Attribute('camera_fade', snode.camera_fade), Attribute('guid', snode.guid), Attribute('mesh_guid', Hex(snode.mesh_guid)), Attribute('nodelevel', snode.nodelevel), Attribute('nodeobject', snode.nodeobject), Attribute('nodesection', snode.nodesection), Attribute('occludes_camera', snode.occludes_camera), Attribute('occludes_light', snode.occludes_light), Attribute('texsetabbr', snode.texsetabbr), ] + [Section('door*', [ Attribute('fardoor', door.fardoor), Attribute('farguid', door.farguid), Attribute('id', door.id), ]) for door in snode.doors]) for snode in self.nodes] return Gas([ Section('t:terrain_nodes,n:siege_node_list', [ Attribute('ambient_color', Hex(self.ambient_color)), Attribute('ambient_intensity', float(self.ambient_intensity)), Attribute('object_ambient_color', Hex(self.object_ambient_color)), Attribute('object_ambient_intensity', float(self.object_ambient_intensity)), Attribute('actor_ambient_color', Hex(self.actor_ambient_color)), Attribute('actor_ambient_intensity', float(self.actor_ambient_intensity)), Attribute('targetnode', self.targetnode) ] + node_sections) ])
def store_generated_objects(self): snci_section = self.gas_dir.get_or_create_subdir( 'index').get_or_create_gas_file( 'streamer_node_content_index').get_gas().get_or_create_section( 'streamer_node_content_index') all_ioids = [ Hex.parse('0x' + str(attr.value)[5:]) for attr in snci_section.get_attrs() ] # all internal object ids (internal meaning without scid range) streamer_node_content_index = {} object_sections = [] last_ioid = 0 for go_data in self.generated_objects_non_interactive: assert isinstance(go_data, GameObjectData) assert go_data.scid is None ioid = last_ioid + 1 while ioid in all_ioids: # find free oid ioid += 1 last_ioid = ioid go_data.scid = Hex.parse('0x{:03X}{:05X}'.format( self.data.scid_range, ioid)) object_sections.append(go_data.make_gas()) node_guid = go_data.placement.position.node_guid if node_guid not in streamer_node_content_index: streamer_node_content_index[node_guid] = [] streamer_node_content_index[node_guid].append(go_data.scid) objects_dir = self.gas_dir.get_or_create_subdir('objects') objects_dir.get_or_create_gas_file( 'non_interactive').get_gas().items.extend(object_sections) snci_attrs = [] for node_guid, oids in streamer_node_content_index.items(): snci_attrs.extend([Attribute(node_guid, oid) for oid in oids]) snci_section.items.extend(snci_attrs)
def store_terrain(self): # index mesh_index = self.terrain.get_mesh_index() mesh_index = { Hex.parse('0x{:03X}{:05X}'.format(self.data.mesh_range, mesh_guid)): mesh_name for mesh_guid, mesh_name in mesh_index.items() } self.gas_dir.create_subdir( 'index', { 'node_mesh_index': Gas([ Section('node_mesh_index', [ Attribute(str(mesh_guid), mesh_name) for mesh_guid, mesh_name in mesh_index.items() ]) ]), 'streamer_node_index': Gas([ Section('streamer_node_index', [ Attribute('*', node.guid) for node in self.terrain.nodes ]) ]) }) # terrain_nodes nodes_gas = NodesGas() nodes_gas.ambient_color = self.terrain.ambient_light.terrain_color nodes_gas.ambient_intensity = self.terrain.ambient_light.terrain_intensity nodes_gas.object_ambient_color = self.terrain.ambient_light.object_color nodes_gas.object_ambient_intensity = self.terrain.ambient_light.object_intensity nodes_gas.actor_ambient_color = self.terrain.ambient_light.actor_color nodes_gas.actor_ambient_intensity = self.terrain.ambient_light.actor_intensity nodes_gas.targetnode = self.terrain.target_node.guid snodes = list() for node in self.terrain.nodes: mesh_guid = Terrain.mesh_index_lookup[node.mesh_name] mesh_guid = Hex.parse('0x{:03X}{:05X}'.format( self.data.mesh_range, mesh_guid)) doors = [ Door(door_id, far_node.guid, far_door) for door_id, (far_node, far_door) in node.doors.items() ] nodesection = Hex( node.section if node.section != -1 else 0xffffffff) nodelevel = Hex(node.section if node.section != -1 else 0xffffffff) nodeobject = Hex( node.section if node.section != -1 else 0xffffffff) snode = SNode(node.guid, mesh_guid, node.texture_set, True, False, False, True, nodesection, nodelevel, nodeobject, doors) snodes.append(snode) nodes_gas.nodes = snodes self.gas_dir.create_subdir('terrain_nodes', {'nodes': nodes_gas.write_gas()})
def main(argv): path = argv[0] hexes = [(Hex.parse(hex_hex.split(',')[0]), Hex.parse(hex_hex.split(',')[1])) for hex_hex in argv[1].split(';')] if os.path.isdir(path): replace_hexes_in_dir(path, hexes) elif os.path.isfile(path): replace_hexes_in_file(path, hexes) else: assert False, f'{path} does not exist' return 0
def ensure_north_vector(self): editor_subdir = self.gas_dir.get_or_create_subdir('editor') if not editor_subdir.has_gas_file('hotpoints'): hotpoints_gas = Gas([ Section('hotpoints', [ Section('t:hotpoint_directional,n:' + str(Hex(1)), [ Attribute('direction', '0,0,-1'), Attribute('id', Hex(1)) ]) ]) ]) editor_subdir.create_gas_file('hotpoints', hotpoints_gas)
def load_data(self): main_file = self.gas_dir.get_gas_file('main') assert main_file is not None main = main_file.get_gas() region_section = main.get_section('t:region,n:region') data = Region.Data() guid = Hex(region_section.get_attr_value('guid')) mesh_range = Hex(region_section.get_attr_value('mesh_range')) scid_range = Hex(region_section.get_attr_value('scid_range')) # assert guid == mesh_range == scid_range # not the case in map_world data.id = guid data.mesh_range = mesh_range data.scid_range = scid_range self.data = data
def new_node_guid(self): guid = Hex.random() assert guid not in [ n.guid for n in self.nodes ], f'new guid {guid} already in existing {len(self.nodes)} nodes' assert guid not in self.all_map_node_ids, f'new guid {guid} already in existing map nodes' return guid
def write_gas(self) -> Gas: se_sections = [ Section(f't:stitch_editor,n:{se.dest_region}', [ Attribute('dest_region', se.dest_region), Section('node_ids', [ Attribute( Hex(stitch_id).to_str_lower(), f'{node_guid},{door}') for stitch_id, (node_guid, door) in se.node_ids.items() ]) ]) for se in self.stitch_editors ] return Gas([ Section('stitch_helper_data', [ Attribute('source_region_guid', Hex(self.source_region_guid)), Attribute('source_region_name', self.source_region_name), ] + se_sections) ])
def get_stitch_id(rx: int, rz: int, hv: bool, xz: int): assert 0 <= rx < 100 assert 0 <= rz < 100 assert 0 <= xz < 0x1000 rx = str(rx).zfill(2) rz = str(rz).zfill(2) hv = '0' if hv else '1' stitch_range = f'0x{rx}{rz}{hv}000' return Hex.parse(stitch_range) + xz
def edit_region_ids(map_name, region_name, mesh_range=None, scid_range=None, guid=None): bits = Bits() assert map_name in bits.maps, map_name _map = bits.maps[map_name] regions = _map.get_regions() assert region_name in regions, region_name region = regions[region_name] if mesh_range is not None: edit_region_mesh_range(region, Hex.parse(mesh_range)) region.gas_dir.clear_cache() region.load_data() if scid_range is not None: edit_region_scid_range(region, Hex.parse(scid_range)) region.gas_dir.clear_cache() region.load_data() if guid is not None: edit_region_guid(region, Hex.parse(guid))
def convert_region(region: Region, nmg: NodeMeshGuids): index_dir = region.gas_dir.get_subdir('index') if index_dir.has_gas_file('node_mesh_index'): return nodes_file = region.gas_dir.get_subdir('terrain_nodes').get_gas_file( 'nodes') node_gas = nodes_file.get_gas() nodes_section: Section = node_gas.items[0] node_sections = nodes_section.get_sections() mesh_guid_attrs = [ns.get_attr('mesh_guid') for ns in node_sections] node_mesh_index = {} node_mesh_guids = nmg.get_node_mesh_guids() for mesh_guid_attr in mesh_guid_attrs: mesh_guid = mesh_guid_attr.value.to_str_lower() assert mesh_guid in node_mesh_guids, str( mesh_guid) + ' is not in ' + str(node_mesh_guids) if mesh_guid not in node_mesh_index: node_mesh_index[mesh_guid] = Hex(len(node_mesh_index) + 1) mesh_id = node_mesh_index[mesh_guid] mesh_guid_attr.value = mesh_id nodes_file.save() node_mesh_index_attrs = [ Attribute(mesh_id, node_mesh_guids[mesh_guid]) for mesh_guid, mesh_id in node_mesh_index.items() ] index_dir.create_gas_file( 'node_mesh_index', Gas([Section('node_mesh_index', node_mesh_index_attrs)])).save() region_data = region.get_data() if region_data.mesh_range == 0: if 0 < region_data.scid_range < Hex.parse('0x00001000'): region_data.mesh_range = region_data.scid_range elif 0 < region_data.id < Hex.parse('0x00001000'): region_data.mesh_range = region_data.id if region_data.mesh_range != 0: region.save() print(f'Converted region {region.get_name()} to NMI')
def edit_region_scid_range(region: Region, new_scid_range: Hex): new_scid_range_str = str(new_scid_range) assert new_scid_range_str.startswith( '0x00000') # only last 3 digits may be used old_scid_range: Hex = region.get_data().scid_range if new_scid_range == old_scid_range: print('no change in scid range') return # check that no region already uses the new scid range other_regions: list[Region] = [ r for r in region.map.get_regions().values() if r.get_name() != region.get_name() ] other_regions_using_new_scid_range = [ r for r in other_regions if r.get_data().scid_range == new_scid_range ] assert len( other_regions_using_new_scid_range ) == 0, f'new scid range is already used by {[r.get_name() for r in other_regions_using_new_scid_range]}' # check that target region is the only one with the old scid range other_regions_using_old_scid_range = [ r for r in other_regions if r.get_data().scid_range == old_scid_range ] assert len( other_regions_using_old_scid_range ) == 0, f'old scid range is also used by {[r.get_name() for r in other_regions_using_old_scid_range]}' print('edit scid range: ' + str(old_scid_range) + ' -> ' + new_scid_range_str) region.get_data().scid_range = new_scid_range region.save() new_scid_prefix = new_scid_range_str[7:] assert len(new_scid_prefix) == 3, new_scid_prefix old_scids = region.get_scids() scid_replacements = [ (old_scid, Hex.parse('0x' + new_scid_prefix + str(old_scid)[5:])) for old_scid in old_scids ] # replace in all files of target region replace_hexes_in_dir(region.gas_dir.path, scid_replacements) # replace in referencing files of all regions replace_hexes_in_dir(region.map.gas_dir.path, scid_replacements, 'elevator.gas') replace_hexes_in_dir(region.map.gas_dir.path, scid_replacements, 'interactive.gas') replace_hexes_in_dir(region.map.gas_dir.path, scid_replacements, 'special.gas')
def test_attribute(self): s = Attribute('s', 'some string') self.assertEqual(None, s.datatype) self.assertEqual('s = some string', str(s)) b = Attribute('b', True) self.assertEqual('b', b.datatype) self.assertEqual('b (b) = true', str(b)) i = Attribute('i', 42) self.assertEqual('i', i.datatype) self.assertEqual('i (i) = 42', str(i)) f = Attribute('f', 13.37) self.assertEqual('f', f.datatype) self.assertEqual('f (f) = 13.370000', str(f)) x = Attribute('x', Hex(4711)) self.assertEqual('x', x.datatype) self.assertEqual('x (x) = 0x00001267', str(x))
def load_terrain(self): node_mesh_index_file = self.gas_dir.get_subdir('index').get_gas_file( 'node_mesh_index') nmi = { Hex.parse(attr.name): attr.value for attr in node_mesh_index_file.get_gas().get_section( 'node_mesh_index').get_attrs() } nodes_gas_file = self.gas_dir.get_subdir('terrain_nodes').get_gas_file( 'nodes') nodes_gas = NodesGas.load(nodes_gas_file) # ambient light ambient_light = AmbientLight(nodes_gas.ambient_color, nodes_gas.ambient_intensity, nodes_gas.object_ambient_color, nodes_gas.object_ambient_intensity, nodes_gas.actor_ambient_color, nodes_gas.actor_ambient_intensity) # nodes nodes = list() nodes_dict = dict() for snode in nodes_gas.nodes: assert snode.mesh_guid in nmi, 'unknown mesh_guid: ' + str( snode.mesh_guid) mesh_name = nmi[snode.mesh_guid] node = TerrainNode(snode.guid, mesh_name, snode.texsetabbr) node.section = snode.nodesection if snode.nodesection != 0xffffffff else -1 node.level = snode.nodelevel if snode.nodelevel != 0xffffffff else -1 node.object = snode.nodeobject if snode.nodeobject != 0xffffffff else -1 nodes_dict[snode.guid] = node nodes.append(node) for snode in nodes_gas.nodes: node = nodes_dict[snode.guid] for door in snode.doors: far_node = nodes_dict[door.farguid] node.doors[door.id] = (far_node, door.fardoor) target_node = nodes_dict[nodes_gas.targetnode] terrain = Terrain() terrain.nodes = nodes terrain.target_node = target_node terrain.ambient_light = ambient_light self.terrain = terrain
def generate_region(_map, region_name, size_x, size_z, args: Args, rt: RegionTiling): print(f'generate region {region_name} {size_x}x{size_z} ({args})') # generate the region! region_data = generate_region_data(size_x, size_z, args, region_name, rt, _map.get_all_node_ids()) if region_data is None: print('all tiles culled - not saving region') return terrain, plants, stitches, decals = region_data # add lighting ambient_color = Hex(0xff8080ff) ambient_intensity = 0.2 terrain.ambient_light.terrain_intensity = ambient_intensity terrain.ambient_light.terrain_color = ambient_color terrain.ambient_light.object_intensity = ambient_intensity terrain.ambient_light.object_color = ambient_color terrain.ambient_light.actor_intensity = ambient_intensity terrain.ambient_light.actor_color = ambient_color dir_lights = [ # daylight from south-east DirectionalLight( color=Color(0xffffffff), draw_shadow=True, intensity=1, occlude_geometry=True, on_timer=True, direction=DirectionalLight.direction_from_orbit_and_azimuth( 225, 45)), # counter-light from north-west DirectionalLight( color=Color(0xffffffff), draw_shadow=False, intensity=0.5, occlude_geometry=False, on_timer=True, direction=DirectionalLight.direction_from_orbit_and_azimuth( 45, 45)) ] # save if region_name in _map.get_regions(): print(f'deleting existing region {region_name}') _map.delete_region(region_name) _map.gas_dir.clear_cache() region_i = rt.cur_x * rt.num_z + rt.cur_z + 1 region: Region = _map.create_region(region_name, region_i) region.terrain = terrain region.decals = DecalsGas(decals) if len(decals) > 0 else None region.lights = dir_lights region.generated_objects_non_interactive = [] if args.start_pos is not None: pos = Position(0, 0, 0, terrain.target_node.guid) start_group_name = args.start_pos _map.load_start_positions() if start_group_name in _map.start_positions.start_groups: _map.start_positions.start_groups[ start_group_name].start_positions = [ StartPos(1, pos, Camera(0.5, 20, 0, pos)) ] else: sg_id = _map.start_positions.new_start_group_id() _map.start_positions.start_groups[start_group_name] = StartGroup( 'Heightmap generated start pos', False, sg_id, 'Heightmap', [StartPos(1, pos, Camera(0.5, 20, 0, pos))]) _map.start_positions.default = start_group_name region.generated_objects_non_interactive.append( GameObjectData( 'trigger_change_mood_box', placement=Placement(position=pos), common=Common(instance_triggers=[ TriggerInstance( 'party_member_within_bounding_box(2,1,2,"on_every_enter")', 'mood_change("map_world_df_r0_2")') ]))) _map.save() if plants is not None: region.generated_objects_non_interactive.extend([ GameObjectData( plant.template_name, placement=Placement(position=plant.position, orientation=Quaternion.rad_to_quat( plant.orientation)), aspect=Aspect(scale_multiplier=plant.size)) for plant in plants ]) if rt and (rt.num_x > 1 or rt.num_z > 1): top_stitches, left_stitches, bottom_stitches, right_stitches = stitches shg = StitchHelperGas(region.data.id, region_name) if top_stitches is not None: shg.stitch_editors.append( StitchEditor(rt.region_name(rt.cur_x, rt.cur_z - 1), top_stitches)) if left_stitches is not None: shg.stitch_editors.append( StitchEditor(rt.region_name(rt.cur_x - 1, rt.cur_z), left_stitches)) if bottom_stitches is not None: shg.stitch_editors.append( StitchEditor(rt.region_name(rt.cur_x, rt.cur_z + 1), bottom_stitches)) if right_stitches is not None: shg.stitch_editors.append( StitchEditor(rt.region_name(rt.cur_x + 1, rt.cur_z), right_stitches)) region.stitch_helper = shg region.save() print(f'new region {region_name} saved')
def store_data(self): main = self.gas_dir.get_or_create_gas_file('main', False).get_gas() region_section = main.get_or_create_section('t:region,n:region') region_section.set_attr_value('guid', Hex(self.data.id)) region_section.set_attr_value('mesh_range', Hex(self.data.mesh_range)) region_section.set_attr_value('scid_range', Hex(self.data.scid_range))