def res_change_io_type_parse(props: Property): """Switch an item to use different inputs or outputs. Must be done before priority level -250. The contents are the same as that allowed in the input BEE2 block in editoritems. """ conf = Config.parse('<ChangeIOType: {:X}>'.format(id(props)), props) def change_item(inst: Entity) -> None: try: item = ITEMS[inst['targetname']] except KeyError: raise ValueError('No item with name "{}"!'.format( inst['targetname'])) item.config = conf # Overwrite these as well. item.enable_cmd = conf.enable_cmd item.disable_cmd = conf.disable_cmd item.sec_enable_cmd = conf.sec_enable_cmd item.sec_disable_cmd = conf.sec_disable_cmd return change_item
def res_make_tag_fizzler_setup(res: Property): """We need this to pre-parse the fizzler type.""" if 'ioconf' in res: fizz_conn = Config.parse('<TAG_FIZZER>', res.find_key('ioconf')) else: fizz_conn = None # The distance from origin the double signs are seperated by. sign_offset = res.int('signoffset', 16) return ( sign_offset, fizz_conn, res['frame_double'], res['frame_single'], res['blue_sign', ''], res['blue_off_sign', ''], res['oran_sign', ''], res['oran_off_sign', ''], )
def _modify_editoritems( self, props: Property, editor: list[EditorItem], pak_id: str, source: str, is_extra: bool, ) -> list[EditorItem]: """Modify either the base or extra editoritems block.""" # We can share a lot of the data, if it isn't changed and we take # care to copy modified parts. editor = list(map(copy.copy, editor)) # Create a list of subtypes in the file, in order to edit. subtype_lookup = [(item, i, subtype) for item in editor for i, subtype in enumerate(item.subtypes)] # Implement overriding palette items for item in props.find_children('Palette'): try: pal_icon = FSPath(item['icon']) except LookupError: pal_icon = None pal_name = item['pal_name', None] # Name for the palette icon try: bee2_icon = img.Handle.parse( item.find_key('BEE2'), pak_id, 64, 64, subfolder='items', ) except LookupError: bee2_icon = None if item.name == 'all': if is_extra: raise Exception('Cannot specify "all" for hidden ' f'editoritems blocks in {source}!') if pal_icon is not None: self.all_icon = pal_icon # If a previous BEE icon was present, remove so we use the VTF. self.icons.pop('all', None) if pal_name is not None: self.all_name = pal_name if bee2_icon is not None: self.icons['all'] = bee2_icon continue try: subtype_ind = int(item.name) subtype_item, subtype_ind, subtype = subtype_lookup[ subtype_ind] except (IndexError, ValueError, TypeError): raise Exception(f'Invalid index "{item.name}" when modifying ' f'editoritems for {source}') subtype_item.subtypes = subtype_item.subtypes.copy() subtype_item.subtypes[subtype_ind] = subtype = copy.deepcopy( subtype) # Overriding model data. if 'models' in item or 'model' in item: subtype.models = [] for prop in item: if prop.name in ('models', 'model'): if prop.has_children(): subtype.models.extend( [FSPath(subprop.value) for subprop in prop]) else: subtype.models.append(FSPath(prop.value)) if item['name', None]: subtype.name = item['name'] # Name for the subtype if bee2_icon: if is_extra: raise ValueError('Cannot specify BEE2 icons for hidden ' f'editoritems blocks in {source}!') self.icons[item.name] = bee2_icon elif pal_icon is not None: # If a previous BEE icon was present, remove so we use the VTF. self.icons.pop(item.name, None) if pal_name is not None: subtype.pal_name = pal_name if pal_icon is not None: subtype.pal_icon = pal_icon if 'Instances' in props: if len(editor) != 1: raise ValueError('Cannot specify instances for multiple ' f'editoritems blocks in {source}!') editor[0].instances = editor[0].instances.copy() editor[0].cust_instances = editor[0].cust_instances.copy() for inst in props.find_children('Instances'): if inst.has_children(): inst_data = InstCount( FSPath(inst['name']), inst.int('entitycount'), inst.int('brushcount'), inst.int('brushsidecount'), ) else: # Allow just specifying the file. inst_data = InstCount(FSPath(inst.value), 0, 0, 0) if inst.real_name.isdecimal(): # Regular numeric try: ind = int(inst.real_name) except IndexError: # This would likely mean there's an extra definition or # something. raise ValueError(f'Invalid index {inst.real_name} for ' f'instances in {source}') from None editor[0].set_inst(ind, inst_data) else: # BEE2 named instance inst_name = inst.name if inst_name.startswith('bee2_'): inst_name = inst_name[5:] editor[0].cust_instances[inst_name] = inst_data.inst # Override IO commands. try: io_props = props.find_key('IOConf') except LookupError: pass else: if len(editor) != 1: raise ValueError('Cannot specify I/O for multiple ' f'editoritems blocks in {source}!') force = io_props['force', ''] editor[0].conn_config = ConnConfig.parse(editor[0].id, io_props) editor[0].force_input = 'in' in force editor[0].force_output = 'out' in force return editor
def res_make_tag_fizzler(vmf: VMF, res: Property): """Add an Aperture Tag Paint Gun activation fizzler. These fizzlers are created via signs, and work very specially. This must be before -250 so it runs before fizzlers and connections. """ if 'ioconf' in res: fizz_conn_conf = Config.parse('<TAG_FIZZER>', res.find_key('ioconf')) else: fizz_conn_conf = None # The distance from origin the double signs are seperated by. sign_offset = res.int('signoffset', 16) inst_frame_double = res['frame_double'] inst_frame_single = res['frame_single'] blue_sign_on = res['blue_sign', ''] blue_sign_off = res['blue_off_sign', ''] oran_sign_on = res['oran_sign', ''] oran_sign_off = res['oran_off_sign', ''] import vbsp if options.get(str, 'game_id') != utils.STEAM_IDS['TAG']: # Abort - TAG fizzlers shouldn't appear in any other game! # So simply remove the fizzler. return Entity.remove def make_tag_fizz(inst: Entity) -> None: """Create the Tag fizzler.""" fizzler: Optional[Fizzler] = None fizzler_item: Optional[Item] = None # Look for the fizzler instance we want to replace. sign_item = connections.ITEMS[inst['targetname']] for conn in list(sign_item.outputs): if conn.to_item.name in FIZZLERS: if fizzler is None: fizzler = FIZZLERS[conn.to_item.name] fizzler_item = conn.to_item else: raise ValueError('Multiple fizzlers attached to a sign!') conn.remove() # Regardless, remove the useless output. sign_item.delete_antlines() if fizzler is None: # No fizzler - remove this sign inst.remove() return if fizzler.fizz_type.id == TAG_FIZZ_ID: LOGGER.warning('Two tag signs attached to one fizzler...') inst.remove() return # Swap to the special Tag Fizzler type. fizzler.fizz_type = FIZZ_TYPES[TAG_FIZZ_ID] # And also swap the connection's type. if fizz_conn_conf is not None: fizzler_item.config = fizz_conn_conf fizzler_item.enable_cmd = fizz_conn_conf.enable_cmd fizzler_item.disable_cmd = fizz_conn_conf.disable_cmd fizzler_item.sec_enable_cmd = fizz_conn_conf.sec_enable_cmd fizzler_item.sec_disable_cmd = fizz_conn_conf.sec_disable_cmd inst_orient = Matrix.from_angle(Angle.from_str(inst['angles'])) # The actual location of the sign - on the wall sign_loc = Vec.from_str(inst['origin']) + Vec(0, 0, -64) @ inst_orient fizz_norm_axis = round(fizzler.normal(), 3).axis() # Now deal with the visual aspect: # Blue signs should be on top. blue_enabled = inst.fixup.bool('$start_enabled') oran_enabled = inst.fixup.bool('$start_reversed') # If True, single-color signs will also turn off the other color. # This also means we always show both signs. # If both are enabled or disabled, this has no effect. disable_other = (not inst.fixup.bool('$disable_autorespawn', True) and blue_enabled != oran_enabled) # Delete fixups now, they aren't useful. inst.fixup.clear() if not blue_enabled and not oran_enabled: # Hide the sign in this case! inst.remove() inst_normal = inst_orient.up() loc = Vec.from_str(inst['origin']) if disable_other or (blue_enabled and oran_enabled): inst['file'] = inst_frame_double conditions.ALL_INST.add(inst_frame_double.casefold()) # On a wall, and pointing vertically if abs(inst_normal.z) < 0.01 and abs(inst_orient.left().z) > 0.01: # They're vertical, make sure blue's on top! blue_loc = Vec(loc.x, loc.y, loc.z + sign_offset) oran_loc = Vec(loc.x, loc.y, loc.z - sign_offset) # If orange is enabled, with two frames put that on top # instead since it's more important if disable_other and oran_enabled: blue_loc, oran_loc = oran_loc, blue_loc else: offset = Vec(0, sign_offset, 0) @ inst_orient blue_loc = loc + offset oran_loc = loc - offset else: inst['file'] = inst_frame_single conditions.ALL_INST.add(inst_frame_single.casefold()) # They're always centered blue_loc = loc oran_loc = loc if inst_normal.z != 0: # If on floors/ceilings, rotate to point at the fizzler! sign_floor_loc = sign_loc.copy() sign_floor_loc.z = 0 # We don't care about z-positions. s, l = Vec.bbox(itertools.chain.from_iterable(fizzler.emitters)) if fizz_norm_axis == 'z': # For z-axis, just compare to the center point of the emitters. sign_dir = ((s.x + l.x) / 2, (s.y + l.y) / 2, 0) - sign_floor_loc else: # For the other two, we compare to the line, # or compare to the closest side (in line with the fizz) if fizz_norm_axis == 'x': # Extends in Y direction other_axis = 'y' side_min = s.y side_max = l.y normal = s.x else: # Extends in X direction other_axis = 'x' side_min = s.x side_max = l.x normal = s.y # Right in line with the fizzler. Point at the closest emitter. if abs(sign_floor_loc[other_axis] - normal) < 32: # Compare to the closest side. sign_dir = min([ sign_floor_loc - Vec.with_axes( fizz_norm_axis, side_min, other_axis, normal, ), sign_floor_loc - Vec.with_axes( fizz_norm_axis, side_max, other_axis, normal, ) ], key=Vec.mag) else: # Align just based on whether we're in front or behind. sign_dir = Vec.with_axes( fizz_norm_axis, normal - sign_floor_loc[fizz_norm_axis]).norm() sign_yaw = math.degrees(math.atan2(sign_dir.y, sign_dir.x)) # Round to nearest 90 degrees # Add 45 so the switchover point is at the diagonals sign_yaw = (sign_yaw + 45) // 90 * 90 # Rotate to fit the instances - south is down sign_yaw = int(sign_yaw - 90) % 360 if inst_normal.z > 0: sign_angle = '0 {} 0'.format(sign_yaw) elif inst_normal.z < 0: # Flip upside-down for ceilings sign_angle = '0 {} 180'.format(sign_yaw) else: raise AssertionError('Cannot be zero here!') else: # On a wall, face upright sign_angle = conditions.PETI_INST_ANGLE[inst_normal.as_tuple()] # If disable_other, we show off signs. Otherwise we don't use that sign. blue_sign = blue_sign_on if blue_enabled else blue_sign_off if disable_other else None oran_sign = oran_sign_on if oran_enabled else oran_sign_off if disable_other else None if blue_sign: conditions.add_inst( vmf, file=blue_sign, targetname=inst['targetname'], angles=sign_angle, origin=blue_loc, ) if oran_sign: conditions.add_inst( vmf, file=oran_sign, targetname=inst['targetname'], angles=sign_angle, origin=oran_loc, ) # Now modify the fizzler... # Subtract the sign from the list of connections, but don't go below # zero fizzler.base_inst.fixup['$connectioncount'] = max( 0, fizzler.base_inst.fixup.int('$connectioncount') - 1) # Find the direction the fizzler normal is. # Signs will associate with the given side! bbox_min, bbox_max = fizzler.emitters[0] sign_center = (bbox_min[fizz_norm_axis] + bbox_max[fizz_norm_axis]) / 2 # Figure out what the sides will set values to... pos_blue = pos_oran = False neg_blue = neg_oran = False if sign_loc[fizz_norm_axis] < sign_center: pos_blue = blue_enabled pos_oran = oran_enabled else: neg_blue = blue_enabled neg_oran = oran_enabled # If it activates the paint gun, use different textures fizzler.tag_on_pos = pos_blue or pos_oran fizzler.tag_on_neg = neg_blue or neg_oran # Now make the trigger ents. We special-case these since they need to # swap # depending on the sign config and position. if vbsp.GAME_MODE == 'COOP': # We need ATLAS-specific triggers. pos_trig = vmf.create_ent(classname='trigger_playerteam') neg_trig = vmf.create_ent(classname='trigger_playerteam') output = 'OnStartTouchBluePlayer' else: pos_trig = vmf.create_ent(classname='trigger_multiple') neg_trig = vmf.create_ent(classname='trigger_multiple') output = 'OnStartTouch' pos_trig['origin'] = neg_trig['origin'] = fizzler.base_inst['origin'] pos_trig['spawnflags'] = neg_trig['spawnflags'] = '1' # Clients Only pos_trig['targetname'] = conditions.local_name(fizzler.base_inst, 'trig_pos') neg_trig['targetname'] = conditions.local_name(fizzler.base_inst, 'trig_neg') pos_trig['startdisabled'] = neg_trig['startdisabled'] = ( not fizzler.base_inst.fixup.bool('start_enabled')) pos_trig.outputs = [ Output(output, neg_trig, 'Enable'), Output(output, pos_trig, 'Disable'), ] neg_trig.outputs = [ Output(output, pos_trig, 'Enable'), Output(output, neg_trig, 'Disable'), ] voice_attr = vbsp.settings['has_attr'] if blue_enabled or disable_other: # If this is blue/oran only, don't affect the other color neg_trig.outputs.append( Output( output, '@BlueIsEnabled', 'SetValue', neg_blue, )) pos_trig.outputs.append( Output( output, '@BlueIsEnabled', 'SetValue', pos_blue, )) if blue_enabled: # Add voice attributes - we have the gun and gel! voice_attr['bluegelgun'] = True voice_attr['bluegel'] = True voice_attr['bouncegun'] = True voice_attr['bouncegel'] = True if oran_enabled or disable_other: neg_trig.outputs.append( Output( output, '@OrangeIsEnabled', 'SetValue', param=srctools.bool_as_int(neg_oran), )) pos_trig.outputs.append( Output( output, '@OrangeIsEnabled', 'SetValue', param=srctools.bool_as_int(pos_oran), )) if oran_enabled: voice_attr['orangegelgun'] = True voice_attr['orangegel'] = True voice_attr['speedgelgun'] = True voice_attr['speedgel'] = True if not oran_enabled and not blue_enabled: # If both are disabled, we must shutdown the gun when touching # either side - use neg_trig for that purpose! # We want to get rid of pos_trig to save ents vmf.remove_ent(pos_trig) neg_trig['targetname'] = conditions.local_name( fizzler.base_inst, 'trig_off') neg_trig.outputs.clear() neg_trig.add_out( Output(output, '@BlueIsEnabled', 'SetValue', param='0')) neg_trig.add_out( Output(output, '@OrangeIsEnabled', 'SetValue', param='0')) # Make the triggers. for bbox_min, bbox_max in fizzler.emitters: bbox_min = bbox_min.copy() - 64 * fizzler.up_axis bbox_max = bbox_max.copy() + 64 * fizzler.up_axis # The triggers are 8 units thick, with a 32-unit gap in the middle neg_min, neg_max = Vec(bbox_min), Vec(bbox_max) neg_min[fizz_norm_axis] -= 24 neg_max[fizz_norm_axis] -= 16 pos_min, pos_max = Vec(bbox_min), Vec(bbox_max) pos_min[fizz_norm_axis] += 16 pos_max[fizz_norm_axis] += 24 if blue_enabled or oran_enabled: neg_trig.solids.append( vmf.make_prism( neg_min, neg_max, mat='tools/toolstrigger', ).solid, ) pos_trig.solids.append( vmf.make_prism( pos_min, pos_max, mat='tools/toolstrigger', ).solid, ) else: # If neither enabled, use one trigger neg_trig.solids.append( vmf.make_prism( neg_min, pos_max, mat='tools/toolstrigger', ).solid, ) return make_tag_fizz
def res_change_io_type_parse(props: Property): """Pre-parse all item types into an anonymous block.""" return Config.parse('<ChangeIOType: {:X}>'.format(id(props)), props)