def res_rand_vec(inst: Entity, res: Property) -> None: """A modification to RandomNum which generates a random vector instead. `decimal`, `seed` and `ResultVar` work like RandomNum. `min_x`, `max_y` etc are used to define the boundaries. If the min and max are equal that number will be always used instead. """ is_float = srctools.conv_bool(res['decimal']) var = res['resultvar', '$random'] set_random_seed(inst, 'e' + res['seed', 'random']) if is_float: func = random.uniform else: func = random.randint value = Vec() for axis in 'xyz': max_val = srctools.conv_float(res['max_' + axis, 0.0]) min_val = srctools.conv_float(res['min_' + axis, 0.0]) if min_val == max_val: value[axis] = min_val else: value[axis] = func(min_val, max_val) inst.fixup[var] = value.join(' ')
def flag_random(inst: Entity, res: Property) -> bool: """Randomly is either true or false.""" if res.has_children(): chance = res['chance', '100'] seed = 'a' + res['seed', ''] else: chance = res.value seed = 'a' # Allow ending with '%' sign chance = srctools.conv_int(chance.rstrip('%'), 100) set_random_seed(inst, seed) return random.randrange(100) < chance
def res_add_variant(inst: Entity, res: Property) -> None: """This allows using a random instance from a weighted group. A suffix will be added in the form `_var4`. Two properties should be given: - `Number`: The number of random instances. - `Weights`: A comma-separated list of weights for each instance. Any variant has a chance of weight/sum(weights) of being chosen: A weight of `2, 1, 1` means the first instance has a 2/4 chance of being chosen, and the other 2 have a 1/4 chance of being chosen. The chosen variant depends on the position, direction and name of the instance. Alternatively, you can use `"variant" "number"` to choose from equally-weighted options. """ set_random_seed(inst, 'variant') conditions.add_suffix(inst, "_var" + str(random.choice(res.value) + 1))
def res_random(inst: Entity, res: Property) -> None: """Randomly choose one of the sub-results to execute. The `chance` value defines the percentage chance for any result to be chosen. `weights` defines the weighting for each result. Both are comma-separated, matching up with the results following. Wrap a set of results in a `group` property block to treat them as a single result to be executed in order. """ # Note: 'global' results like "Has" won't delete themselves! # Instead they're replaced by 'dummy' results that don't execute. # Otherwise the chances would be messed up. seed, chance, weight, results = res.value # type: str, float, List[int], List[Property] set_random_seed(inst, seed) if random.randrange(100) > chance: return ind = random.choice(weight) choice = results[ind] if choice.name == 'nop': pass elif choice.name == 'group': for sub_res in choice: should_del = Condition.test_result( inst, sub_res, ) if should_del is RES_EXHAUSTED: # This Result doesn't do anything! sub_res.name = 'nop' sub_res.value = None else: should_del = Condition.test_result( inst, choice, ) if should_del is RES_EXHAUSTED: choice.name = 'nop' choice.value = None
def res_rand_num(inst: Entity, res: Property) -> None: """Generate a random number and save in a fixup value. If 'decimal' is true, the value will contain decimals. 'max' and 'min' are inclusive. 'ResultVar' is the variable the result will be saved in. If 'seed' is set, it will be used to keep the value constant across map recompiles. This should be unique. """ is_float = srctools.conv_bool(res['decimal']) max_val = srctools.conv_float(res['max', 1.0]) min_val = srctools.conv_float(res['min', 0.0]) var = res['resultvar', '$random'] seed = 'd' + res['seed', 'random'] set_random_seed(inst, seed) if is_float: func = random.uniform else: func = random.randint inst.fixup[var] = str(func(min_val, max_val))
def res_rand_inst_shift(inst: Entity, res: Property) -> None: """Randomly shift a instance by the given amounts. The positions are local to the instance. """ ( min_x, max_x, min_y, max_y, min_z, max_z, seed, ) = res.value # type: float, float, float, float, float, float, str set_random_seed(inst, seed) offset = Vec( random.uniform(min_x, max_x), random.uniform(min_y, max_y), random.uniform(min_z, max_z), ) offset.rotate_by_str(inst['angles']) origin = Vec.from_str(inst['origin']) origin += offset inst['origin'] = origin
def res_set_tile(inst: Entity, res: Property) -> None: """Set 4x4 parts of a tile to the given values. `Offset` defines the position of the upper-left tile in the grid. Each `Tile` section defines a row of the positions to edit like so: "Tile" "bbbb" "Tile" "b..b" "Tile" "b..b" "Tile" "bbbb" If `Force` is true, the specified tiles will override any existing ones and create the tile if necessary. Otherwise they will be merged in - white/black tiles will not replace tiles set to nodraw or void for example. `chance`, if specified allows producing irregular tiles by randomly not changing the tile. If you need less regular placement (other orientation, precise positions) use a bee2_template_tilesetter in a template. Allowed tile characters: - `W`: White tile. - `w`: White 4x4 only tile. - `B`: Black tile. - `b`: Black 4x4 only tile. - `g`: The side/bottom of goo pits. - `n`: Nodraw surface. - `i`: Invert the tile surface, if black/white. - `1`: Convert to a 1x1 only tile, if a black/white tile. - `4`: Convert to a 4x4 only tile, if a black/white tile. - `.`: Void (remove the tile in this position). - `_` or ` `: Placeholder (don't modify this space). - `x`: Cutout Tile (Broken) - `o`: Cutout Tile (Partial) """ origin = Vec.from_str(inst['origin']) angles = Vec.from_str(inst['angles']) offset = (res.vec('offset', -48, 48) - (0, 0, 64)).rotate(*angles) offset += origin norm = Vec(0, 0, 1).rotate(*angles) force_tile = res.bool('force') tiles: List[str] = [ row.value for row in res if row.name in ('tile', 'tiles') ] if not tiles: raise ValueError('No "tile" parameters in SetTile!') chance = srctools.conv_float(res['chance', '100'].rstrip('%'), 100.0) if chance < 100.0: conditions.set_random_seed(inst, 'tile' + res['seed', '']) for y, row in enumerate(tiles): for x, val in enumerate(row): if val in '_ ': continue if chance < 100.0 and random.uniform(0, 100) > chance: continue pos = Vec(32 * x, -32 * y, 0).rotate(*angles) + offset if val == '4': size = tiling.TileSize.TILE_4x4 elif val == '1': size = tiling.TileSize.TILE_1x1 elif val == 'i': size = None else: try: new_tile = tiling.TILETYPE_FROM_CHAR[val] # type: tiling.TileType except KeyError: LOGGER.warning('Unknown tiletype "{}"!', val) else: tiling.edit_quarter_tile(pos, norm, new_tile, force_tile) continue # Edit the existing tile. try: tile, u, v = tiling.find_tile(pos, norm, force_tile) except KeyError: LOGGER.warning( 'Expected tile, but none found: {}, {}', pos, norm, ) continue if size is None: # Invert the tile. tile[u, v] = tile[u, v].inverted continue # Unless forcing is enabled don't alter the size of GOO_SIDE. if tile[u, v].is_tile and tile[u, v] is not tiling.TileType.GOO_SIDE: tile[u, v] = tiling.TileType.with_color_and_size( size, tile[u, v].color ) elif force_tile: # If forcing, make it black. Otherwise no need to change. tile[u, v] = tiling.TileType.with_color_and_size( size, tiling.Portalable.BLACK )