async def arrange_trade(team: str, partner: str, items) -> DiscordAction: pair_list = [] try: pair_list = to_pair_list(items) except ValueError as _: return Message("Input list is badly formatted.") sub = get_sub(team) partner_sub = get_sub(partner) if sub and partner_sub: return Message(await sub.inventory.begin_trade(partner_sub, pair_list)) return Message("Didn't recognise the submarine asked for.")
async def print_map(team: str, options: Sequence[str] = ("w", "d", "s", "a", "m", "e"), show_hidden: bool = False) -> DiscordAction: """ Prints the map from the perspective of one submarine, or all if team is None. """ subs = [] max_options = ["w", "d", "s", "t", "n", "a", "j", "m", "e"] if options is True: options = MAX_OPTIONS options = list(filter(lambda v: v in max_options, options)) if team is None: subs = get_sub_objects() else: sub = get_sub(team) if sub is None: return FAIL_REACT else: subs = [sub] map_string, map_arr = draw_map(subs, list(options), show_hidden) map_json = json.dumps(map_arr) async with httpx.AsyncClient() as client: url = MAP_DOMAIN + "/api/map/" res = await client.post(url, data={ "map": map_string, "key": MAP_TOKEN, "names": map_json }) if res.status_code == 200: final_url = MAP_DOMAIN + res.json()['url'] return Message(f"The map is visible here: {final_url}") return FAIL_REACT
async def broadcast(self, content: str): if self.last_comms + COMMS_COOLDOWN > now(): return False my_pos = self.sub.movement.get_position() for subname in get_subs(): if subname == self.sub._name: continue sub = get_sub(subname) dist = diagonal_distance(my_pos, sub.movement.get_position()) garbled = self.garble(content, dist) if garbled is not None: await sub.send_message( f"**Message received from {self.sub.name()}**:\n`{garbled}`\n**END MESSAGE**", "captain") for npcid in get_npcs(): npc = get_npc(npcid) dist = diagonal_distance(my_pos, npc.get_position()) garbled = self.garble(content, dist) if garbled is not None: await npc.send_message( f"**Message received from {self.sub.name()}**:\n`{garbled}`\n**END MESSAGE**", "") self.last_comms = now() return True
def hits(self, x: int, y: int) -> Dict[str, List[Entity]]: # Returns a list of indirect and direct hits. indirect = [] direct = [] for subname in get_subs(): sub = get_sub(subname) pos = sub.movement.get_position() distance = diagonal_distance(pos, (x, y)) if distance == 0: direct.append(sub) elif distance == 1: indirect.append(sub) for npcid in get_npcs(): npc = get_npc(npcid) pos = npc.get_position() distance = diagonal_distance(pos, (x, y)) if distance == 0: direct.append(npc) elif distance == 1: indirect.append(npc) shuffle(indirect) shuffle(direct) return {"indirect": indirect, "direct": direct}
async def register(category : discord.CategoryChannel, x : int, y : int, keyword : str) -> DiscordAction: """ Registers a team, setting them up with everything they could need. Requires a category with the required subchannels. ONLY RUNNABLE BY CONTROL. """ if add_team(category.name.lower(), category, x, y, keyword): sub = get_sub(category.name.lower()) if sub: await sub.send_to_all(f"Channel registered for sub **{category.name.title()}**.") return OKAY_REACT return FAIL_REACT
def zoom_in(x: int, y: int, loop) -> DiscordAction: if in_world(x, y): report = f"Report for square **({x}, {y})**\n" report += get_square(x, y).square_status() + "\n\n" # See if any subs are here, and if so print their status. subs_in_square = filtered_teams( lambda sub: sub.movement.x == x and sub.movement.y == y) for subname in subs_in_square: sub = get_sub(subname) report += sub.status_message(loop) + "\n\n" return Message(report) return Message("Chosen square is outside the world boundaries!")
async def make_submarine(guild : discord.Guild, name : str, captain : discord.Member, engineer : discord.Member, scientist : discord.Member, x : int, y : int, keyword : str) -> DiscordAction: """ Makes a submarine with the name <name> and members Captain, Engineer and Scientist. Creates a category with <name>, then channels for each player. Then creates the relevant roles (if they don't exist already), and assigns them to players. Finally, we register this team as a submarine. """ if get_sub(name): return FAIL_REACT category = await guild.create_category_channel(name) # Create roles if needed. captain_role = await create_or_return_role(guild, "captain", mentionable=True) engineer_role = await create_or_return_role(guild, "engineer", mentionable=True) scientist_role = await create_or_return_role(guild, "scientist", mentionable=True) control_role = await create_or_return_role(guild, CONTROL_ROLE, hoist=True, mentionable=True) altantis_role = await create_or_return_role(guild, "ALTANTIS", hoist=True) submarine_role = await create_or_return_role(guild, name, hoist=True) specific_capt = await create_or_return_role(guild, f"captain-{name}") specific_engi = await create_or_return_role(guild, f"engineer-{name}") specific_sci = await create_or_return_role(guild, f"scientist-{name}") # Add roles to players. await captain.add_roles(captain_role, submarine_role, specific_capt) await engineer.add_roles(engineer_role, submarine_role, specific_engi) await scientist.add_roles(scientist_role, submarine_role, specific_sci) # Add perms to created text channels. def allow_control_and_one(channel): if channel is not None: return { guild.default_role: discord.PermissionOverwrite(read_messages=False), channel: discord.PermissionOverwrite(read_messages=True), control_role: discord.PermissionOverwrite(read_messages=True), altantis_role: discord.PermissionOverwrite(read_messages=True) } else: return { guild.default_role: discord.PermissionOverwrite(read_messages=False), control_role: discord.PermissionOverwrite(read_messages=True), altantis_role: discord.PermissionOverwrite(read_messages=True) } await category.create_text_channel("captain", overwrites=allow_control_and_one(specific_capt)) await category.create_text_channel("engineer", overwrites=allow_control_and_one(specific_engi)) await category.create_text_channel("scientist", overwrites=allow_control_and_one(specific_sci)) await category.create_text_channel("secret", overwrites=allow_control_and_one(None)) await category.create_text_channel("control-room", overwrites=allow_control_and_one(submarine_role)) await category.create_voice_channel("submarine", overwrites=allow_control_and_one(submarine_role)) return await register(category, x, y, keyword)
async def explode(pos: Tuple[int, int], power: int, sub_exclusions: List[str] = [], npc_exclusions: List[int] = []): """ Makes an explosion in pos, dealing power damage to the centre square, power-1 to the surrounding ones, power-2 to those that surround and so on. """ from ALTANTIS.subs.state import get_subs, get_sub from ALTANTIS.npcs.npc import get_npcs, get_npc for subname in get_subs(): if subname in sub_exclusions: continue sub = get_sub(subname) sub_pos = sub.movement.get_position() sub_dist = diagonal_distance(pos, sub_pos) damage = power - sub_dist if damage > 0: await sub.send_message(f"Explosion in {pos}!", "captain") sub.damage(damage) for npcid in get_npcs(): if npcid in npc_exclusions: continue npc_obj = get_npc(npcid) npc_pos = npc_obj.get_position() npc_dist = diagonal_distance(pos, npc_pos) damage = power - npc_dist if damage > 0: await npc_obj.send_message(f"Explosion in {pos}!", "captain") npc_obj.damage(damage)
def is_active_sub(subname): sub = get_sub(subname) if not sub: return False return sub.power.activated()
async def perform_timestep(counter: int): """ Does all time-related stuff, including movement, power changes and so on. Called at a time interval, when allowed. """ global NO_SAVE NO_SAVE = True print(f"Running turn {counter}.") def is_active_sub(subname): sub = get_sub(subname) if not sub: return False return sub.power.activated() # Get all active subs. (Can you tell I'm a functional programmer?) # Note: we still collect all messages for all subs, as there are some # messages that inactive subs should receive. subsubset: List[str] = list(filter(is_active_sub, get_subs())) submessages: Dict[str, Dict[str, str]] = { i: { "engineer": "", "captain": "", "scientist": "" } for i in get_subs() } message_opening: str = f"---------**TURN {counter}**----------\n" # Emergency messaging for subname in subsubset: sub = get_sub(subname) if sub.power.total_power == 1: emergency_message = f"EMERGENCY!!! {random.choice(emergencies)}\n" submessages[subname]["captain"] += emergency_message submessages[subname]["scientist"] += emergency_message submessages[subname]["engineer"] += emergency_message # Power management for subname in subsubset: sub = get_sub(subname) power_message = sub.power.apply_power_schedule() if power_message: power_message = f"{power_message}\n" submessages[subname]["captain"] += power_message submessages[subname]["engineer"] += power_message # Weapons for subname in subsubset: sub = get_sub(subname) weapons_message = sub.weapons.weaponry_tick() if weapons_message: weapons_message = f"{weapons_message}\n" submessages[subname]["captain"] += weapons_message # NPCs await npc_tick() # Map map_tick() # The crane for subname in subsubset: sub = get_sub(subname) crane_message = await sub.inventory.crane_tick() if crane_message: crane_message = f"{crane_message}\n" submessages[subname]["scientist"] += crane_message # Movement, trade and puzzles for subname in subsubset: sub = get_sub(subname) move_message, trade_messages = await sub.movement.movement_tick() if move_message: move_message = f"{move_message}\n" submessages[subname]["captain"] += move_message for target in trade_messages: submessages[target]["captain"] += trade_messages[target] + "\n" # Scanning (as we enter a new square only) for subname in subsubset: sub = get_sub(subname) scan_message = sub.scan.scan_string() if scan_message != "": submessages[subname]["captain"] += scan_message submessages[subname]["scientist"] += scan_message # Postponed events for subname in subsubset: sub = get_sub(subname) await sub.upgrades.postponed_tick() # Damage for subname in get_subs(): sub = get_sub(subname) damage_message = await sub.power.damage_tick() if damage_message: damage_message = f"{damage_message}\n" submessages[subname]["captain"] += damage_message submessages[subname]["engineer"] += damage_message submessages[subname]["scientist"] += damage_message for subname in get_subs(): messages = submessages[subname] sub = get_sub(subname) if messages["captain"] == "": if subname not in subsubset: messages[ "captain"] = "Your submarine is deactivated so nothing happened.\n" else: messages[ "captain"] = "Your submarine is active, but there is nothing to notify you about.\n" await sub.send_message(f"{message_opening}{messages['captain'][:-1]}", "captain") if messages["engineer"] != "": await sub.send_message( f"{message_opening}{messages['engineer'][:-1]}", "engineer") if messages["scientist"] != "": await sub.send_message( f"{message_opening}{messages['scientist'][:-1]}", "scientist") NO_SAVE = False save_game()
def get_parent(self) -> Optional[Submarine]: if self.parent is None: return None return get_sub(self.parent)
def add_npc_to_map(ntype: str, x: int, y: int, team: Optional[str]) -> DiscordAction: sub = None if team and get_sub(team): sub = team return Message(add_npc(ntype, x, y, sub))
def explore_submap(pos: Tuple[int, int], dist: int, sub_exclusions: Collection[str] = (), npc_exclusions: Collection[int] = (), with_distance: bool = False) -> List[str]: """ Explores the area centered around pos = (cx, cy) spanning distance dist. Returns all outward_broadcast events (as a list) formatted for output. Ignores any NPCs or subs with a name included in exclusions. """ events = [] (cx, cy) = pos # First, map squares. for i in range(-dist, dist + 1): x = cx + i if x < 0 or x >= X_LIMIT: continue for j in range(-dist, dist + 1): y = cy + j if y < 0 or y >= Y_LIMIT: continue this_dist = diagonal_distance((0, 0), (i, j)) event = get_square(x, y).outward_broadcast(dist - this_dist) if event != "": direction = determine_direction((cx, cy), (x, y)) if direction is None: event = f"{event} - in your current square!" else: distance_measure = "" if with_distance: distance_measure = f" at a distance of {this_dist} away" event = f"{event} - in direction {direction.upper()}{distance_measure}!" events.append(event) # Then, submarines. for subname in get_subs(): if subname in sub_exclusions: continue sub = get_sub(subname) sub_pos = sub.movement.get_position() sub_dist = diagonal_distance(pos, sub_pos) # If out of range, drop it. if sub_dist > dist: continue event = sub.scan.outward_broadcast(dist - sub_dist) direction = determine_direction(pos, sub_pos) if direction is None: event = f"{event} in your current square!" else: event = f"{event} in direction {direction.upper()}!" events.append(event) # Finally, NPCs. for npcid in get_npcs(): if npcid in npc_exclusions: continue npc_obj = get_npc(npcid) npc_pos = npc_obj.get_position() npc_dist = diagonal_distance(pos, npc_pos) if npc_dist > dist: continue event = npc_obj.outward_broadcast(dist - npc_dist) direction = determine_direction(pos, npc_pos) if direction is None: event = f"{event} in your current square!" else: event = f"{event} in direction {direction.upper()}!" events.append(event) return events