Exemple #1
0
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.")
Exemple #2
0
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
Exemple #3
0
    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
Exemple #4
0
    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}
Exemple #5
0
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
Exemple #6
0
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!")
Exemple #7
0
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)
Exemple #8
0
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)
Exemple #9
0
 def is_active_sub(subname):
     sub = get_sub(subname)
     if not sub: return False
     return sub.power.activated()
Exemple #10
0
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()
Exemple #11
0
 def get_parent(self) -> Optional[Submarine]:
     if self.parent is None:
         return None
     return get_sub(self.parent)
Exemple #12
0
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))
Exemple #13
0
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