about summary refs log tree commit diff stats
path: root/tools/validator/structs.h
blob: d1d45f27feb47b118581d6f64f751fe08d9ef645 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#ifndef TOOLS_VALIDATOR_STRUCTS_H_
#define TOOLS_VALIDATOR_STRUCTS_H_

#include <map>
#include <string>
#include <vector>

#include "proto/human.pb.h"
#include "util/identifiers.h"

namespace com::fourisland::lingo2_archipelago {

struct MalformedIdentifiers {
  std::vector<PaintingIdentifier> paintings;
  std::vector<PanelIdentifier> panels;
  std::vector<KeyholderIdentifier> keyholders;

  bool HasAny() const {
    return !paintings.empty() || !panels.empty() || !keyholders.empty();
  }
};

struct GameNodeInfo {
  bool defined = false;
  int uses = 0;
};

struct MapInfo {
  std::map<std::string, GameNodeInfo> game_nodes;
};

struct RoomInfo {
  std::vector<HumanRoom> definitions;

  std::vector<DoorIdentifier> doors_referenced_by;
  std::vector<PanelIdentifier> panels_referenced_by;
  std::vector<HumanConnection> connections_referenced_by;
};

struct DoorInfo {
  std::vector<HumanDoor> definitions;
  bool has_id = false;

  std::vector<HumanConnection> connections_referenced_by;
  std::vector<DoorIdentifier> doors_referenced_by;
  std::vector<PanelIdentifier> panels_referenced_by;
  std::vector<PaintingIdentifier> paintings_referenced_by;
  std::vector<PortIdentifier> ports_referenced_by;
  std::vector<std::string> progressives_referenced_by;
  std::vector<std::string> door_groups_referenced_by;

  MalformedIdentifiers malformed_identifiers;
};

struct PortInfo {
  std::vector<HumanPort> definitions;

  std::vector<HumanConnection> connections_referenced_by;
  std::vector<HumanConnection> target_connections_referenced_by;
};

struct PaintingInfo {
  std::vector<HumanPainting> definitions;

  std::vector<HumanConnection> connections_referenced_by;
  std::vector<HumanConnection> target_connections_referenced_by;
  std::vector<DoorIdentifier> doors_referenced_by;
};

struct ProxyInfo {
  std::vector<Proxy> definitions;

  std::vector<HumanConnection> connections_referenced_by;
  std::vector<DoorIdentifier> doors_referenced_by;
};

struct PanelInfo {
  std::vector<HumanPanel> definitions;
  bool has_id = false;

  std::string map_area_name;

  std::vector<HumanConnection> connections_referenced_by;
  std::vector<HumanConnection> target_connections_referenced_by;
  std::vector<DoorIdentifier> doors_referenced_by;

  std::map<std::string, ProxyInfo> proxies;
};

struct KeyholderInfo {
  std::vector<HumanKeyholder> definitions;
  bool has_id = false;

  std::vector<DoorIdentifier> doors_referenced_by;
};

using LetterIdentifier = std::tuple<char, bool>;

struct LetterInfo {
  std::vector<RoomIdentifier> defined_in;
  bool has_id = false;
};

struct EndingInfo {
  std::vector<RoomIdentifier> defined_in;
  bool has_id = false;

  std::vector<DoorIdentifier> doors_referenced_by;
};

struct PanelNameInfo {
  std::vector<PanelIdentifier> panels_used_by;
};

struct ProgressiveInfo {
  std::vector<HumanProgressive> definitions;
  bool has_id = false;

  std::vector<DoorIdentifier> malformed_doors;
};

struct DoorGroupInfo {
  std::vector<HumanDoorGroup> definitions;
  bool has_id = false;

  std::vector<DoorIdentifier> malformed_doors;
};

struct CollectedInfo {
  std::map<std::string, MapInfo> maps;
  std::map<RoomIdentifier, RoomInfo, RoomIdentifierLess> rooms;
  std::map<DoorIdentifier, DoorInfo, DoorIdentifierLess> doors;
  std::map<PortIdentifier, PortInfo, PortIdentifierLess> ports;
  std::map<PaintingIdentifier, PaintingInfo, PaintingIdentifierLess> paintings;
  std::map<PanelIdentifier, PanelInfo, PanelIdentifierLess> panels;
  std::map<KeyholderIdentifier, KeyholderInfo, KeyholderIdentifierLess>
      keyholders;
  std::map<LetterIdentifier, LetterInfo> letters;
  std::map<std::string, EndingInfo> endings;
  std::map<std::string, PanelNameInfo> panel_names;
  std::map<std::string, ProgressiveInfo> progressives;
  std::map<std::string, DoorGroupInfo> door_groups;
};

}  // namespace com::fourisland::lingo2_archipelago

#endif /* TOOLS_VALIDATOR_STRUCTS_H_ */
on_package(self, cmd: str, args: dict): if cmd == "RoomInfo": self.seed_name = args.get("seed_name", None) elif cmd == "Connected": self.slot_data = args.get("slot_data", None) if self.game_ctx.server is not None: self.game_ctx.send_connected() self.game_ctx.tracker.setup_slot(self.slot_data) elif cmd == "RoomUpdate": if self.game_ctx.server is not None: self.game_ctx.send_update_locations(args["checked_locations"]) elif cmd == "ReceivedItems": self.game_ctx.tracker.set_collected_items(self.items_received) if self.game_ctx.server is not None: cur_index = 0 items = [] for item in args["items"]: index = cur_index + args["index"] cur_index += 1 item_msg = { "id": item.item, "index": index, "flags": item.flags, "text": self.item_names.lookup_in_slot(item.item, self.slot), } if item.player != self.slot: item_msg["sender"] = self.player_names.get(item.player) items.append(item_msg) self.game_ctx.send_item_received(items) if any(ItemClassification.progression in ItemClassification(item.flags) for item in args["items"]): self.game_ctx.send_accessible_locations() elif cmd == "PrintJSON": if self.game_ctx.server is not None: if "receiving" in args and "item" in args and args["item"].player == self.slot: item_name = self.item_names.lookup_in_slot(args["item"].item, args["receiving"]) location_name = self.location_names.lookup_in_slot(args["item"].location, args["item"].player) receiver_name = self.player_names.get(args["receiving"]) if args["type"] == "Hint" and not args.get("found", False): self.game_ctx.send_hint_received(item_name, location_name, receiver_name, args["item"].flags, int(args["receiving"]) == self.slot) elif args["receiving"] != self.slot: self.game_ctx.send_item_sent_notification(item_name, receiver_name, args["item"].flags) parts = [] for message_part in args["data"]: if "type" not in message_part and "text" in message_part: parts.append({"type": "text", "text": message_part["text"]}) elif message_part["type"] == "player_id": parts.append({ "type": "player", "text": self.player_names.get(int(message_part["text"])), "self": int(int(message_part["text"]) == self.slot), }) elif message_part["type"] == "item_id": parts.append({ "type": "item", "text": self.item_names.lookup_in_slot(int(message_part["text"]), message_part["player"]), "flags": message_part["flags"], }) elif message_part["type"] == "location_id": parts.append({ "type": "location", "text": self.location_names.lookup_in_slot(int(message_part["text"]), message_part["player"]) }) elif "text" in message_part: parts.append({"type": "text", "text": message_part["text"]}) self.game_ctx.send_text_message(parts) elif cmd == "LocationInfo": if self.game_ctx.server is not None: locations = [] for location in args["locations"]: locations.append({ "id": location.location, "item": self.item_names.lookup_in_slot(location.item, location.player), "player": self.player_names.get(location.player), "flags": location.flags, "self": int(location.player) == self.slot, }) self.game_ctx.send_location_info(locations) if cmd in ["Connected", "RoomUpdate"]: self.game_ctx.tracker.set_checked_locations(self.checked_locations) async def pipe_loop(ctx: Lingo2GameContext): while not ctx.client.exit_event.is_set(): try: socket = await websockets.connect("ws://localhost", port=PORT, ping_timeout=None, ping_interval=None, max_size=MESSAGE_MAX_SIZE) ctx.server = Endpoint(socket) logger.info("Connected to Lingo 2!") if ctx.client.auth is not None: ctx.send_connected() ctx.send_accessible_locations() async for data in ctx.server.socket: for msg in decode(data): await process_game_cmd(ctx, msg) except ConnectionRefusedError: logger.info("Could not connect to Lingo 2.") finally: ctx.server = None async def process_game_cmd(ctx: Lingo2GameContext, args: dict): cmd = args["cmd"] if cmd == "Connect": server = args.get("server") player = args.get("player") password = args.get("password") if password != "": server_address = f"{player}:{password}@{server}" else: server_address = f"{player}:None@{server}" async_start(ctx.client.connect(server_address), name="client connect") elif cmd == "Disconnect": async_start(ctx.client.disconnect(), name="client disconnect") elif cmd in ["Sync", "LocationChecks", "Say", "StatusUpdate", "LocationScouts"]: async_start(ctx.client.send_msgs([args]), name="client forward") elif cmd == "Quit": ctx.client.exit_event.set() async def run_game(): exe_file = settings.get_settings().lingo2_options.exe_file if Lingo2World.zip_path is not None: # This is a packaged apworld. init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") init_path = Utils.local_path("data", "lingo2_init.tscn") with open(init_path, "wb") as file_handle: file_handle.write(init_scene) subprocess.Popen( [ exe_file, "--scene", init_path, "--", str(Lingo2World.zip_path.absolute()), ], cwd=os.path.dirname(exe_file), ) else: # The world is unzipped and being run in source. subprocess.Popen( [ exe_file, "--scene", Utils.local_path("worlds", "lingo2", "client", "run_from_source.tscn"), "--", Utils.local_path("worlds", "lingo2", "client"), ], cwd=os.path.dirname(exe_file), ) def client_main(*launch_args: str) -> None: async def main(args): async_start(run_game()) client_ctx = Lingo2ClientContext(args.connect, args.password) game_ctx = Lingo2GameContext() client_ctx.game_ctx = game_ctx game_ctx.client = client_ctx client_ctx.server_task = asyncio.create_task(server_loop(client_ctx), name="ServerLoop") if gui_enabled: client_ctx.run_gui() client_ctx.run_cli() pipe_task = asyncio.create_task(pipe_loop(game_ctx), name="GameWatcher") try: await pipe_task except Exception as e: logger.exception(e) await client_ctx.exit_event.wait() client_ctx.ui.stop() await client_ctx.shutdown() Utils.init_logging("Lingo2Client", exception_logger="Client") import colorama parser = get_base_parser(description="Lingo 2 Archipelago Client") parser.add_argument('--name', default=None, help="Slot Name to connect as.") parser.add_argument("url", nargs="?", help="Archipelago connection url") args = parser.parse_args(launch_args) args = handle_url_arg(args, parser=parser) colorama.just_fix_windows_console() asyncio.run(main(args)) colorama.deinit()