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 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_sub_objects from ALTANTIS.npcs.npc import get_npc_objects for sub in get_sub_objects(): if sub._name in sub_exclusions: continue 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 npc in get_npc_objects(): if npc.id in npc_exclusions: continue npc_pos = npc.get_position() npc_dist = diagonal_distance(pos, npc_pos) damage = power - npc_dist if damage > 0: await npc.send_message(f"Explosion in {pos}!", "captain") npc.damage(damage)
def hits(self, x: int, y: int) -> Dict[str, List[Entity]]: # Returns a list of indirect and direct hits. indirect: List[Entity] = [] direct: List[Entity] = [] for sub in get_sub_objects(): pos = sub.movement.get_position() distance = diagonal_distance(pos, (x, y)) if distance == 0: direct.append(sub) elif distance == 1: indirect.append(sub) for npc in get_npc_objects(): 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 broadcast(self, content : str) -> str: if self.last_comms + COMMS_COOLDOWN > now(): return f"The comms system is still cooling down! (Requires {int(self.last_comms + COMMS_COOLDOWN - now())}s more.)" if self.sub.power.get_power("comms") == 0: return "Cannot use comms system that isn't powered!" my_pos = self.sub.movement.get_position() for sub in get_sub_objects(): if sub._name == self.sub._name: continue 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 npc in get_npc_objects(): 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 "Transmitted message."
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(sub): 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[Submarine] = list(filter(is_active_sub, get_sub_objects())) 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 sub in subsubset: if sub.power.total_power == 1: subname = sub._name 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 sub in subsubset: power_message = sub.power.apply_power_schedule() if power_message: subname = sub._name power_message = f"{power_message}\n" submessages[subname]["captain"] += power_message submessages[subname]["engineer"] += power_message # Weapons for sub in subsubset: weapons_message = sub.weapons.weaponry_tick() if weapons_message: weapons_message = f"{weapons_message}\n" submessages[sub._name]["captain"] += weapons_message # NPCs await npc_tick() # Map map_tick() # The crane for sub in subsubset: crane_message = await sub.inventory.crane_tick() if crane_message: crane_message = f"{crane_message}\n" submessages[sub._name]["scientist"] += crane_message # Movement, trade and puzzles for sub in subsubset: move_message, trade_messages = await sub.movement.movement_tick() if move_message: move_message = f"{move_message}\n" submessages[sub._name]["captain"] += move_message for target in trade_messages: submessages[target]["captain"] += trade_messages[target] + "\n" # Scanning (as we enter a new square only) for sub in subsubset: scan_message = sub.scan.scan_string() if scan_message != "": subname = sub._name submessages[subname]["captain"] += scan_message submessages[subname]["scientist"] += scan_message # Postponed events for sub in subsubset: await sub.upgrades.postponed_tick() # Damage for sub in get_sub_objects(): damage_message = await sub.power.damage_tick() if damage_message: subname = sub._name damage_message = f"{damage_message}\n" submessages[subname]["captain"] += damage_message submessages[subname]["engineer"] += damage_message submessages[subname]["scientist"] += damage_message for sub in get_sub_objects(): messages = submessages[sub._name] if messages["captain"] == "": if sub._name not in map(lambda s: s._name, subsubset): if sub.power.total_power <= 0: messages[ "captain"] = "Your submarine is **dead** so nothing happened.\n" else: 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 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)) sq = get_square(x, y) if sq is None: continue event = sq.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 sub in get_sub_objects(): if sub._name in sub_exclusions: continue 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 npc in get_npc_objects(): if npc.id in npc_exclusions: continue npc_pos = npc.get_position() npc_dist = diagonal_distance(pos, npc_pos) if npc_dist > dist: continue event = npc.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