def parse(cls, vmf: VMF, ent: Entity, radius: float) -> 'Dropper': """Scan the map applying dropper tweaks, then create the Dropper object.""" filter_name = ent['filtername'] template_name = ent['template'] for cube_filter in vmf.search(filter_name): break else: raise ValueError( f'No filter "{filter_name}" for dropper at {ent["origin"]}!') for template in vmf.search(template_name): break else: raise ValueError( f'No template "{template_name}" for dropper at {ent["origin"]}!' ) best_cube = None best_dist = math.inf radius **= 2 ref_pos = Vec.from_str(cube_filter['origin']) for cube in vmf.by_class['prop_weighted_cube'] | vmf.by_class[ 'prop_monster_box']: dist = (Vec.from_str(cube['origin']) - ref_pos).mag_sq() if dist > radius or dist > best_dist: continue best_dist = dist best_cube = cube if best_cube is None: LOGGER.warning( 'Cube dropper at {} has no cube. Generating standard one...', ref_pos) best_cube = vmf.create_ent( 'prop_weighted_cube', angles='0 0 0', newskins='1', skintype='0', cubetype='0', skin='0', paintpower='4', model=CUBE_MODEL, ) # Now adjust the cube for dropper use. best_cube.make_unique('dropper_cube') best_cube['origin'] = ent['origin'] # Only regular cubes can disable funnelling, but frankenturrets # require being in box form. if best_cube['classname'] == 'prop_monster_box': best_cube['startasbox'] = '1' else: best_cube['allowfunnel'] = '0' # Copy the cube name to filter and dropper. cube_filter['filtername'] = best_cube['targetname'] for i in range(1, 10): if not template[f'Template{i:02}']: template[f'Template{i:02}'] = best_cube['targetname'] break else: raise ValueError(f'No spare slots for template "{template_name}"!') # Add fizzle outputs if enabled. if srctools.conv_bool(ent['autorespawn']): best_cube.outputs += [ out for out in ent.outputs if out.output.casefold() == 'onfizzled' ] ent.add_out(Output(Dropper.pass_out_name, template, 'ForceSpawn')) return Dropper(ent, template, best_cube)