def res_change_outputs_setup(res: Property): return [ ( Output.parse_name(prop.real_name), Output.parse_name(prop.value) ) for prop in res ]
def res_linked_cube(inst: Entity, res: Property): """Marks a cube to link it to a dropper. This assumes some things about the item. """ time = inst.fixup.int('$timer_delay') # Portal 2 bug - when loading existing maps, timers are set to 3... if not (3 < time <= 30): # Infinite or 3-second - this behaviour is disabled.. return if time in LINKED_CUBES: raise Exception( 'Two cubes have the same ' '"linkage" value set ({})!'.format( time, ) ) resp_out_name, resp_out = Output.parse_name(res.value) LINKED_CUBES[time] = ( inst, inst.fixup['$cube_type'], resp_out_name, resp_out, )
def res_cust_output_setup(res: Property): conds = [ Condition.parse(sub_res) for sub_res in res if sub_res.name == 'targcondition' ] outputs = list(res.find_all('addOut')) dec_con_count = srctools.conv_bool(res["decConCount", '0'], False) sign_type = IND_PANEL_TYPES.get(res['sign_type', None], None) if sign_type is None: sign_act = sign_deact = (None, '') else: # The outputs which trigger the sign. sign_act = Output.parse_name(res['sign_activate', '']) sign_deact = Output.parse_name(res['sign_deactivate', '']) return outputs, dec_con_count, conds, sign_type, sign_act, sign_deact
def res_locking_input(inst: Entity, res: Property): """Executed on the input item, and evaluates to True if successful. The parameter is an `instance:name;Input` value, which resets the item. This must be executed after the `MarkLocking` results have run. """ from vbsp import IND_ITEM_NAMES, IND_PANEL_NAMES, VMF in_name, in_inp = Output.parse_name(res.value) targets = { out.target for out in inst.outputs # Skip toggle or indicator panel items. if out.target not in IND_ITEM_NAMES } # No outputs, or 2+ - we can't convert in that case if len(targets) != 1: return False target, = targets try: targ_inst, targ_out_name, targ_out, out_relay = LOCKABLE_ITEMS[target] except KeyError: # Some other item... return False # Remove the indicator panel instances. ind_panels = { out.target for out in inst.outputs # Skip toggle or indicator panel items. if out.target in IND_PANEL_NAMES } for pan_inst in VMF.by_class['func_instance']: if pan_inst['targetname'] in ind_panels: pan_inst.remove() # Add an output pointing in the opposite direction. if out_relay is None: targ_inst.add_out(Output( out=targ_out, inst_out=targ_out_name, targ=inst['targetname'], inp=in_inp, inst_in=in_name, )) else: from conditions.instances import add_global_input add_global_input( inst, in_name, in_inp, rl_name=out_relay, output=targ_out, ) return True
def res_global_input_setup(res: Property): target = res['target', ''] or None name = res['name', ''] or None output = res['output', 'OnTrigger'] param = res['param', ''] delay = srctools.conv_float(res['delay', '']) inp_name, inp_command = Output.parse_name(res['input']) return name, inp_name, inp_command, output, delay, param, target
def res_locking_output(inst: Entity, res: Property): """Marks an output item for locked connections. The parameter is an `instance:name;Output` value, which is fired when the item resets. This must be executed before `LockingIO`. This only applies if `$connectioncount` is 1. """ # Items with more than one connection have AND logic in the mix - it makes # it unsafe to lock the input item. if inst.fixup['$connectioncount'] != '1': return if res.has_children(): name, output = Output.parse_name(res['output']) relay_name = res['rl_name', None] else: name, output = Output.parse_name(res.value) relay_name = None LOCKABLE_ITEMS[inst['targetname']] = inst, name, output, relay_name
def res_change_inputs_setup(res: Property): vals = {} for prop in res: out_key = Output.parse_name(prop.real_name) if prop.has_children(): vals[out_key] = ( prop['inst_in', None], prop['input'], prop['params', ''], srctools.conv_float(prop['delay', 0.0]), 1 if srctools.conv_bool(prop['only_once', '0']) else -1, ) else: vals[out_key] = None return vals
def res_global_input_setup(res: Property): if res.has_children(): name = res['name', ''] inp_name, inp_command = Output.parse_name(res['input']) return name, Output( out=res['output', 'OnTrigger'], targ=res['target', ''], inp=inp_command, inst_in=inp_name, delay=srctools.conv_float(res['delay', '']), param=res['param', ''], ) else: out = Output.parse(res) out.output = '' # Don't need to store GlobalInput... return '', out
def res_global_input_setup(res: Property) -> tuple[str, Output]: """Pre-parse the global input.""" if res.has_children(): name = res['name', ''] if not name and res.bool('alsoonload'): name = ON_LOAD inp_name, inp_command = Output.parse_name(res['input']) return name, Output( out=res['output', 'OnTrigger'], targ=res['target', ''], inp=inp_command, inst_in=inp_name, delay=srctools.conv_float(res['delay', '']), param=res['param', ''], ) else: out = Output.parse(res) out.output = '' # Don't need to store GlobalInput... return '', out
def res_resizeable_trigger(vmf: VMF, res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename. * `markerItem`: The item's ID * `previewConf`: A item config which enables/disables the preview overlay. * `previewInst`: An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. * `previewMat`: If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. * `previewScale`: The scale for the func_brush materials. * `previewActivate`, `previewDeactivate`: The VMF output to turn the previewInst on and off. * `triggerActivate, triggerDeactivate`: The `instance:name;Output` outputs used when the trigger turns on or off. * `coopVar`: The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. * `coopOnce`: If true, kill the manager after it first activates. * `keys`: A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. * `localkeys`: The same as above, except values will be changed to use instance-local names. """ marker = instanceLocs.resolve(res['markerInst']) marker_names = set() for inst in vmf.by_class['func_instance']: if inst['file'].casefold() in marker: marker_names.add(inst['targetname']) # Unconditionally delete from the map, so it doesn't # appear even if placed wrongly. inst.remove() if not marker_names: # No markers in the map - abort return RES_EXHAUSTED item_id = res['markerItem'] # Synthesise the item type used for the final trigger. item_type_sp = connections.ItemType( id=item_id + ':TRIGGER', output_act=Output.parse_name(res['triggerActivate', 'OnStartTouchAll']), output_deact=Output.parse_name(res['triggerDeactivate', 'OnEndTouchAll']), ) # For Coop, we add a logic_coop_manager in the mix so both players can # be handled. try: coop_var = res['coopVar'] except LookupError: coop_var = item_type_coop = None coop_only_once = False else: coop_only_once = res.bool('coopOnce') item_type_coop = connections.ItemType( id=item_id + ':TRIGGER_COOP', output_act=Output.parse_name(res['coopActivate', 'OnChangeToAllTrue']), output_deact=Output.parse_name(res['coopDeactivate', 'OnChangeToAnyFalse']), ) # Display preview overlays if it's preview mode, and the config is true pre_act = pre_deact = None if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] preview_scale = res.float('previewScale', 0.25) # None if not found. with suppress(LookupError): pre_act = Output.parse(res.find_key('previewActivate')) with suppress(LookupError): pre_deact = Output.parse(res.find_key('previewDeactivate')) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' preview_scale = 0.25 # Now go through each brush. # We do while + pop to allow removing both names each loop through. todo_names = set(marker_names) while todo_names: targ = todo_names.pop() mark1 = connections.ITEMS.pop(targ) for conn in mark1.outputs: if conn.to_item.name in marker_names: mark2 = conn.to_item conn.remove() # Delete this connection. todo_names.discard(mark2.name) del connections.ITEMS[mark2.name] break else: if not mark1.inputs: # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. mark2 = mark1 else: # It's a marker with an input, the other in the pair # will handle everything. # But reinstate it in ITEMS. connections.ITEMS[targ] = mark1 continue inst1 = mark1.inst inst2 = mark2.inst is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and ( inst1.fixup.bool(coop_var) or inst2.fixup.bool(coop_var)) bbox_min, bbox_max = Vec.bbox(Vec.from_str(inst1['origin']), Vec.from_str(inst2['origin'])) origin = (bbox_max + bbox_min) / 2 # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vmf.create_ent( classname='trigger_multiple', # Default targetname=targ, origin=origin, angles='0 0 0', ) trig_ent.solids = [ vmf.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent = manager = vmf.create_ent( classname='logic_coop_manager', targetname=conditions.local_name(inst, 'man'), origin=origin, ) item = connections.Item( out_ent, item_type_coop, mark1.ant_floor_style, mark1.ant_wall_style, ) if coop_only_once: # Kill all the ents when both players are present. manager.add_out( Output('OnChangeToAllTrue', manager, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'), ) else: item = connections.Item( trig_ent, item_type_sp, mark1.ant_floor_style, mark1.ant_wall_style, ) # Register, and copy over all the antlines. connections.ITEMS[item.name] = item item.ind_panels = mark1.ind_panels | mark2.ind_panels item.antlines = mark1.antlines | mark2.antlines item.shape_signs = mark1.shape_signs + mark2.shape_signs if preview_mat: preview_brush = vmf.create_ent( classname='func_brush', parentname=targ, origin=origin, Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vmf.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: pre_inst = vmf.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=inst2['origin'], ) if pre_act is not None: out = pre_act.copy() out.inst_out, out.output = item.output_act() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) if pre_deact is not None: out = pre_deact.copy() out.inst_out, out.output = item.output_deact() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) for conn in mark1.outputs | mark2.outputs: conn.from_item = item return RES_EXHAUSTED
def res_resizeable_trigger(res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: 'markerInst': <ITEM_ID:1,2> value referencing the marker instances, or a filename. 'markerItem': The item's ID 'previewVar': A stylevar which enables/disables the preview overlay. 'previewinst': An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. 'previewMat': If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. 'previewScale': The scale for the func_brush materials. 'previewActivate', 'previewDeactivate': The 'instance:name;Input' value to turn the previewInst on and off. 'triggerActivate, triggerDeactivate': The outputs used when the trigger turns on or off. 'coopVar': The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. 'coopActivate, coopDeactivate': The outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. 'coopOnce': If true, kill the manager after it first activates. 'keys': A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. 'localkeys': The same as above, except values will be changed to use instance-local names. """ marker = resolve_inst(res['markerInst']) markers = {} for inst in vbsp.VMF.by_class['func_instance']: if inst['file'].casefold() in marker: markers[inst['targetname']] = inst if not markers: # No markers in the map - abort return RES_EXHAUSTED trig_act = res['triggerActivate', 'OnStartTouchAll'] trig_deact = res['triggerDeactivate','OnEndTouchAll'] coop_var = res['coopVar', None] coop_act = res['coopActivate', 'OnChangeToAllTrue'] coop_deact = res['coopDeactivate', 'OnChangeToAnyFalse'] coop_only_once = res.bool('coopOnce') marker_connection = conditions.CONNECTIONS[res['markerItem'].casefold()] mark_act_name, mark_act_out = marker_connection.out_act mark_deact_name, mark_deact_out = marker_connection.out_deact del marker_connection preview_var = res['previewVar', ''].casefold() # Display preview overlays if it's preview mode, and the style var is true # or does not exist if vbsp.IS_PREVIEW and (not preview_var or vbsp.settings['style_vars'][preview_var]): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] pre_act_name, pre_act_inp = Output.parse_name( res['previewActivate', '']) pre_deact_name, pre_deact_inp = Output.parse_name( res['previewDeactivate', '']) preview_scale = srctools.conv_float(res['previewScale', '0.25'], 0.25) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' pre_act_name = pre_deact_name = None pre_act_inp = pre_deact_inp = '' preview_scale = 0.25 # Now convert each brush # Use list() to freeze it, allowing us to delete from the dict for targ, inst in list(markers.items()): # type: str, VLib.Entity for out in inst.output_targets(): if out in markers: other = markers[out] # type: Entity del markers[out] # Don't let it get repeated break else: if inst.fixup['$connectioncount'] == '0': # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. other = inst else: continue # It's a marker with an input, the other in the pair # will handle everything. for ent in {inst, other}: # Only do once if inst == other ent.remove() is_coop = vbsp.GAME_MODE == 'COOP' and ( inst.fixup.bool(coop_var) or other.fixup.bool(coop_var) ) bbox_min, bbox_max = Vec.bbox( Vec.from_str(inst['origin']), Vec.from_str(other['origin']) ) # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vbsp.VMF.create_ent( classname='trigger_multiple', # Default # Use the 1st instance's name - that way other inputs control the # trigger itself. targetname=targ, origin=inst['origin'], angles='0 0 0', ) trig_ent.solids = [ vbsp.VMF.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent_name = conditions.local_name(inst, 'man') out_ent = vbsp.VMF.create_ent( classname='logic_coop_manager', targetname=out_ent_name, origin=inst['origin'] ) if coop_only_once: # Kill all the ents when both players are present. out_ent.add_out( Output('OnChangeToAllTrue', out_ent_name, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', out_ent_name, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', out_ent_name, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', out_ent_name, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', out_ent_name, 'SetStateBFalse'), ) act_out = coop_act deact_out = coop_deact else: act_out = trig_act deact_out = trig_deact if preview_mat: preview_brush = vbsp.VMF.create_ent( classname='func_brush', parentname=targ, origin=inst['origin'], Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vbsp.VMF.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: vbsp.VMF.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=other['origin'], ) if pre_act_name and trig_act: out_ent.add_out(Output( trig_act, targ + '_preview', inst_in=pre_act_name, inp=pre_act_inp, )) if pre_deact_name and trig_deact: out_ent.add_out(Output( trig_deact, targ + '_preview', inst_in=pre_deact_name, inp=pre_deact_inp, )) # Now copy over the outputs from the markers, making it work. for out in inst.outputs + other.outputs: # Skip the output joining the two markers together. if out.target == other['targetname']: continue if out.inst_out == mark_act_name and out.output == mark_act_out: ent_out = act_out elif out.inst_out == mark_deact_name and out.output == mark_deact_out: ent_out = deact_out else: continue # Skip this output - it's somehow invalid for this item. if not ent_out: continue # Allow setting the output to "" to skip out_ent.add_out(Output( ent_out, out.target, inst_in=out.inst_in, inp=out.input, param=out.params, delay=out.delay, times=out.times, )) return RES_EXHAUSTED
def get_input(prop_name: str): """Parse an input command.""" try: return Output.parse_name(conf[prop_name]) except IndexError: return None
def parse(item_id: str, conf: Property): """Read the item type info from the given config.""" def get_outputs(prop_name): """Parse all the outputs with this name.""" return [ Output.parse(prop) for prop in conf.find_all(prop_name) # Allow blank to indicate no output. if prop.value != '' ] enable_cmd = get_outputs('enable_cmd') disable_cmd = get_outputs('disable_cmd') lock_cmd = get_outputs('lock_cmd') unlock_cmd = get_outputs('unlock_cmd') inf_lock_only = conf.bool('inf_lock_only') timer_done_cmd = get_outputs('timer_done_cmd') if 'timer_sound_pos' in conf: timer_sound_pos = conf.vec('timer_sound_pos') force_timer_sound = conf.bool('force_timer_sound') else: timer_sound_pos = None force_timer_sound = False try: input_type = InputType( conf['Type', 'default'].casefold() ) except ValueError: raise ValueError('Invalid input type "{}": {}'.format( item_id, conf['type'], )) from None invert_var = conf['invertVar', '0'] try: spawn_fire = FeatureMode(conf['spawnfire', 'never'].casefold()) except ValueError: # Older config option - it was a bool for always/never. spawn_fire_bool = conf.bool('spawnfire', None) if spawn_fire_bool is None: raise # Nope, not a bool. spawn_fire = FeatureMode.ALWAYS if spawn_fire_bool else FeatureMode.NEVER try: sec_spawn_fire = FeatureMode(conf['sec_spawnfire', 'never'].casefold()) except ValueError: # Default to primary value. sec_spawn_fire = FeatureMode.NEVER if input_type is InputType.DUAL: sec_enable_cmd = get_outputs('sec_enable_cmd') sec_disable_cmd = get_outputs('sec_disable_cmd') try: default_dual = CONN_TYPE_NAMES[ conf['Default_Dual', 'primary'].casefold() ] except KeyError: raise ValueError('Invalid default type for "{}": {}'.format( item_id, conf['Default_Dual'], )) from None # We need an affinity to use when nothing else specifies it. if default_dual is ConnType.DEFAULT: raise ValueError('Must specify a default type for "{}"!'.format( item_id, )) from None sec_invert_var = conf['sec_invertVar', '0'] else: # No dual type, set to dummy values. sec_enable_cmd = [] sec_disable_cmd = [] default_dual = ConnType.DEFAULT sec_invert_var = '' try: output_type = CONN_TYPE_NAMES[ conf['DualType', 'default'].casefold() ] except KeyError: raise ValueError('Invalid output affinity for "{}": {}'.format( item_id, conf['DualType'], )) from None def get_input(prop_name: str): """Parse an input command.""" try: return Output.parse_name(conf[prop_name]) except IndexError: return None out_act = get_input('out_activate') out_deact = get_input('out_deactivate') out_lock = get_input('out_lock') out_unlock = get_input('out_unlock') timer_start = timer_stop = None if 'out_timer_start' in conf: timer_start = [ Output.parse_name(prop.value) for prop in conf.find_all('out_timer_start') if prop.value ] if 'out_timer_stop' in conf: timer_stop = [ Output.parse_name(prop.value) for prop in conf.find_all('out_timer_stop') if prop.value ] return Config( item_id, default_dual, input_type, spawn_fire, invert_var, enable_cmd, disable_cmd, sec_spawn_fire, sec_invert_var, sec_enable_cmd, sec_disable_cmd, output_type, out_act, out_deact, lock_cmd, unlock_cmd, out_lock, out_unlock, inf_lock_only, timer_sound_pos, timer_done_cmd, force_timer_sound, timer_start, timer_stop, )
def res_resizeable_trigger(vmf: VMF, res: Property): """Replace two markers with a trigger brush. This is run once to affect all of an item. Options: * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename. * `markerItem`: The item's ID * `previewConf`: A item config which enables/disables the preview overlay. * `previewInst`: An instance to place at the marker location in preview mode. This should contain checkmarks to display the value when testing. * `previewMat`: If set, the material to use for an overlay func_brush. The brush will be parented to the trigger, so it vanishes once killed. It is also non-solid. * `previewScale`: The scale for the func_brush materials. * `previewActivate`, `previewDeactivate`: The VMF output to turn the previewInst on and off. * `triggerActivate, triggerDeactivate`: The `instance:name;Output` outputs used when the trigger turns on or off. * `coopVar`: The instance variable which enables detecting both Coop players. The trigger will be a trigger_playerteam. * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used when coopVar is enabled. These should be suitable for a logic_coop_manager. * `coopOnce`: If true, kill the manager after it first activates. * `keys`: A block of keyvalues for the trigger brush. Origin and targetname will be set automatically. * `localkeys`: The same as above, except values will be changed to use instance-local names. """ marker = instanceLocs.resolve(res['markerInst']) marker_names = set() for inst in vmf.by_class['func_instance']: if inst['file'].casefold() in marker: marker_names.add(inst['targetname']) # Unconditionally delete from the map, so it doesn't # appear even if placed wrongly. inst.remove() if not marker_names: # No markers in the map - abort return RES_EXHAUSTED item_id = res['markerItem'] # Synthesise the item type used for the final trigger. item_type_sp = connections.ItemType( id=item_id + ':TRIGGER', output_act=Output.parse_name(res['triggerActivate', 'OnStartTouchAll']), output_deact=Output.parse_name(res['triggerDeactivate', 'OnEndTouchAll']), ) # For Coop, we add a logic_coop_manager in the mix so both players can # be handled. try: coop_var = res['coopVar'] except LookupError: coop_var = item_type_coop = None coop_only_once = False else: coop_only_once = res.bool('coopOnce') item_type_coop = connections.ItemType( id=item_id + ':TRIGGER_COOP', output_act=Output.parse_name( res['coopActivate', 'OnChangeToAllTrue'] ), output_deact=Output.parse_name( res['coopDeactivate', 'OnChangeToAnyFalse'] ), ) # Display preview overlays if it's preview mode, and the config is true pre_act = pre_deact = None if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False): preview_mat = res['previewMat', ''] preview_inst_file = res['previewInst', ''] preview_scale = res.float('previewScale', 0.25) # None if not found. with suppress(LookupError): pre_act = Output.parse(res.find_key('previewActivate')) with suppress(LookupError): pre_deact = Output.parse(res.find_key('previewDeactivate')) else: # Deactivate the preview_ options when publishing. preview_mat = preview_inst_file = '' preview_scale = 0.25 # Now go through each brush. # We do while + pop to allow removing both names each loop through. todo_names = set(marker_names) while todo_names: targ = todo_names.pop() mark1 = connections.ITEMS.pop(targ) for conn in mark1.outputs: if conn.to_item.name in marker_names: mark2 = conn.to_item conn.remove() # Delete this connection. todo_names.discard(mark2.name) del connections.ITEMS[mark2.name] break else: if not mark1.inputs: # If the item doesn't have any connections, 'connect' # it to itself so we'll generate a 1-block trigger. mark2 = mark1 else: # It's a marker with an input, the other in the pair # will handle everything. # But reinstate it in ITEMS. connections.ITEMS[targ] = mark1 continue inst1 = mark1.inst inst2 = mark2.inst is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and ( inst1.fixup.bool(coop_var) or inst2.fixup.bool(coop_var) ) bbox_min, bbox_max = Vec.bbox( Vec.from_str(inst1['origin']), Vec.from_str(inst2['origin']) ) origin = (bbox_max + bbox_min) / 2 # Extend to the edge of the blocks. bbox_min -= 64 bbox_max += 64 out_ent = trig_ent = vmf.create_ent( classname='trigger_multiple', # Default targetname=targ, origin=origin, angles='0 0 0', ) trig_ent.solids = [ vmf.make_prism( bbox_min, bbox_max, mat=const.Tools.TRIGGER, ).solid, ] # Use 'keys' and 'localkeys' blocks to set all the other keyvalues. conditions.set_ent_keys(trig_ent, inst, res) if is_coop: trig_ent['spawnflags'] = '1' # Clients trig_ent['classname'] = 'trigger_playerteam' out_ent = manager = vmf.create_ent( classname='logic_coop_manager', targetname=conditions.local_name(inst, 'man'), origin=origin, ) item = connections.Item( out_ent, item_type_coop, mark1.ant_floor_style, mark1.ant_wall_style, ) if coop_only_once: # Kill all the ents when both players are present. manager.add_out( Output('OnChangeToAllTrue', manager, 'Kill'), Output('OnChangeToAllTrue', targ, 'Kill'), ) trig_ent.add_out( Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'), Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'), Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'), Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'), ) else: item = connections.Item( trig_ent, item_type_sp, mark1.ant_floor_style, mark1.ant_wall_style, ) # Register, and copy over all the antlines. connections.ITEMS[item.name] = item item.ind_panels = mark1.ind_panels | mark2.ind_panels item.antlines = mark1.antlines | mark2.antlines item.shape_signs = mark1.shape_signs + mark2.shape_signs if preview_mat: preview_brush = vmf.create_ent( classname='func_brush', parentname=targ, origin=origin, Solidity='1', # Not solid drawinfastreflection='1', # Draw in goo.. # Disable shadows and lighting.. disableflashlight='1', disablereceiveshadows='1', disableshadowdepth='1', disableshadows='1', ) preview_brush.solids = [ # Make it slightly smaller, so it doesn't z-fight with surfaces. vmf.make_prism( bbox_min + 0.5, bbox_max - 0.5, mat=preview_mat, ).solid, ] for face in preview_brush.sides(): face.scale = preview_scale if preview_inst_file: pre_inst = vmf.create_ent( classname='func_instance', targetname=targ + '_preview', file=preview_inst_file, # Put it at the second marker, since that's usually # closest to antlines if present. origin=inst2['origin'], ) if pre_act is not None: out = pre_act.copy() out.inst_out, out.output = item.output_act() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) if pre_deact is not None: out = pre_deact.copy() out.inst_out, out.output = item.output_deact() out.target = conditions.local_name(pre_inst, out.target) out_ent.add_out(out) for conn in mark1.outputs | mark2.outputs: conn.from_item = item return RES_EXHAUSTED
def res_linked_cube_dropper(drp_inst: Entity, res: Property): """Link a cube and dropper together, to preplace the cube at a location.""" time = drp_inst.fixup.int('$timer_delay') # Portal 2 bug - when loading existing maps, timers are set to 3... if not (3 < time <= 30): # Infinite or 3-second - this behaviour is disabled.. return try: cube_inst, cube_type, resp_out_name, resp_out = LINKED_CUBES[time] except KeyError: raise Exception('Unknown cube "linkage" value ({}) in dropper!'.format( time, )) # Force the dropper to match the cube.. # = cube_type # Set auto-drop to False (so there isn't two cubes), # and auto-respawn to True (so it actually functions). drp_inst.fixup['$disable_autodrop'] = '1' drp_inst.fixup['$disable_autorespawn'] = '0' fizz_out_name, fizz_out = Output.parse_name(res['FizzleOut']) # Output to destroy the cube when the dropper is triggered externally. drp_inst.add_out(Output( inst_out=fizz_out_name, out=fizz_out, targ=local_name(cube_inst, 'cube'), inp='Dissolve', only_once=True, )) # Cube items don't have proxies, so we need to use AddOutput # after it's created (@relay_spawn_3's time). try: relay_spawn_3 = GLOBAL_INPUT_ENTS['@relay_spawn_3'] except KeyError: relay_spawn_3 = GLOBAL_INPUT_ENTS['@relay_spawn_3'] = cube_inst.map.create_ent( classname='logic_relay', targetname='@relay_spawn_3', origin=cube_inst['origin'], ) respawn_inp = list(res.find_all('RespawnIn')) # There's some voice-logic specific to companion cubes. respawn_inp.extend(res.find_all( 'RespawnCcube' if drp_inst.fixup['$cube_type'] == '1' else 'RespawnCube' )) for inp in respawn_inp: resp_in_name, resp_in = inp.value.split(':', 1) out = Output( out='OnFizzled', targ=drp_inst, inst_in=resp_in_name, inp=resp_in, only_once=True, ) relay_spawn_3.add_out(Output( out='OnTrigger', targ=local_name(cube_inst, 'cube'), inp='AddOutput', param=out.gen_addoutput(), only_once=True, delay=0.01, ))
def parse(cls, conf: Property): """Read in a fizzler from a config.""" fizz_id = conf['id'] item_ids = [prop.value.casefold() for prop in conf.find_all('item_id')] try: model_name_type = ModelName(conf['NameType', 'same'].casefold()) except ValueError: LOGGER.warning('Bad model name type: "{}"', conf['NameType']) model_name_type = ModelName.SAME model_local_name = conf['ModelName', ''] if not model_local_name: # We can't rename without a local name. model_name_type = ModelName.SAME inst = {} for inst_type, is_static in itertools.product(FizzInst, (False, True)): inst_type_name = inst_type.value + ('_static' if is_static else '') inst[inst_type, is_static] = instances = [ file for prop in conf.find_all(inst_type_name) for file in instanceLocs.resolve(prop.value) ] # Allow specifying weights to bias model locations weights = conf[inst_type_name + '_weight', ''] if weights: # Produce the weights, then process through the original # list to build a new one with repeated elements. inst[inst_type, is_static] = instances = [ instances[i] for i in conditions.weighted_random( len(instances), weights) ] # If static versions aren't given, reuse non-static ones. # We do False, True so it's already been calculated. if not instances and is_static: inst[inst_type, True] = inst[inst_type, False] if not inst[FizzInst.BASE, False]: LOGGER.warning('No base instance set! for "{}"!', fizz_id) voice_attrs = [] for prop in conf.find_all('Has'): if prop.has_children(): for child in prop: voice_attrs.append(child.name.casefold()) else: voice_attrs.append(prop.value.casefold()) out_activate = conf['OutActivate', None] if out_activate is not None: out_activate = Output.parse_name(out_activate) out_deactivate = conf['OutDeactivate', None] if out_deactivate is not None: out_deactivate = Output.parse_name(out_deactivate) pack_lists = {prop.value for prop in conf.find_all('Pack')} pack_lists_static = { prop.value for prop in conf.find_all('PackStatic') } brushes = [FizzlerBrush.parse(prop) for prop in conf.find_all('Brush')] beams = [] # type: List[FizzBeam] for beam_prop in conf.find_all('Beam'): offsets = [ Vec.from_str(off.value) for off in beam_prop.find_all('pos') ] keys = Property('', [ beam_prop.find_key('Keys', []), beam_prop.find_key('LocalKeys', []) ]) beams.append( FizzBeam( offsets, keys, beam_prop.int('RandSpeedMin', 0), beam_prop.int('RandSpeedMax', 0), )) try: temp_conf = conf.find_key('TemplateBrush') except NoKeyError: temp_brush_keys = temp_min = temp_max = temp_single = None else: temp_brush_keys = Property('--', [ temp_conf.find_key('Keys'), temp_conf.find_key('LocalKeys', []), ]) # Find and load the templates. temp_min = temp_conf['Left', None] temp_max = temp_conf['Right', None] temp_single = temp_conf['Single', None] return FizzlerType( fizz_id, item_ids, voice_attrs, pack_lists, pack_lists_static, model_local_name, model_name_type, out_activate, out_deactivate, brushes, beams, inst, temp_brush_keys, temp_min, temp_max, temp_single, )
def parse(item, key): """Parse the output value, handling values that aren't present.""" val = item[key, ''] if not val: return None, '' return Output.parse_name(val)