home · contact · privacy
Fix buggy healthy_addch().
[plomrogue] / roguelike-client
1 #!/usr/bin/python3
2
3 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
4 # or any later version. For details on its copyright, license, and warranties,
5 # see the file NOTICE in the root directory of the PlomRogue source package.
6
7
8 import curses
9 import os
10 import signal
11 import time
12
13 from client.config.world_data import world_data
14 from client.config.io import io
15 from client.config.commands import commands
16 from client.window_management import redraw_windows, set_windows, draw_screen, \
17                                      stdscr
18 from client.query_mapcell import query_mapcell
19
20
21 message_queue = {
22     "open_end": False,
23     "messages": []
24 }
25
26
27 def read_worldstate():
28     global redraw_windows
29     if not os.access(io["path_worldstate"], os.F_OK):
30         msg = "No world state file found at " + io["path_worldstate"] + "."
31         raise SystemExit(msg)
32     read_anew = False
33     worldstate_file = open(io["path_worldstate"], "r")
34     turn_string = worldstate_file.readline()
35     if int(turn_string) != world_data["turn"]:
36         read_anew = True
37     if not read_anew: # In rare cases, world may change, but not turn number.
38         mtime = os.stat(io["path_worldstate"])
39         if mtime != read_worldstate.last_checked_mtime:
40             read_worldstate.last_checked_mtime = mtime
41             read_anew = True
42     if read_anew:
43         # TODO: Hardcode order of necessary fields, ensure order dependencies.
44         redraw_windows = True
45         old_inventory_size = len(world_data["inventory"])
46         world_data["turn"] = int(turn_string)
47         for entry in io["worldstate_read_order"]:
48             if entry[1] == "int":
49                 if 2 == len(entry):
50                     world_data[entry[0]] = int(worldstate_file.readline())
51                 elif 3 == len(entry):
52                     world_data[entry[0]][entry[2]] = \
53                             int(worldstate_file.readline())
54             elif entry[1] == "lines":
55                 world_data[entry[0]] = []
56                 while True:
57                     line = worldstate_file.readline().replace("\n", "")
58                     if line == '%':
59                         break
60                     world_data[entry[0]] += [line]
61             elif entry[1] == "map":
62                 world_data[entry[0]] = ""
63                 for i in range(world_data["map_size"]):
64                     line = worldstate_file.readline().replace("\n", "")
65                     world_data[entry[0]] += line
66         if not world_data["look_mode"]:
67             world_data["map_center"] = world_data["avatar_position"][:]
68         if world_data["inventory_selection"] > 0 and \
69                 len(world_data["inventory"]) < old_inventory_size:
70             world_data["inventory_selection"] -= 1
71     worldstate_file.close()
72 read_worldstate.last_checked_mtime = -1
73
74
75 def read_message_queue():
76     global redraw_windows
77     while (len(message_queue["messages"]) > 1
78         or (len(message_queue["messages"]) == 1
79             and not message_queue["open_end"])):
80         message = message_queue["messages"].pop(0)
81         if message == "THINGS_HERE START":
82             read_message_queue.parse_thingshere = True
83             world_data["look"] = []
84         elif message == "THINGS_HERE END":
85             read_message_queue.parse_thingshere = False
86             if world_data["look"] == []:
87                 world_data["look"] = ["(none known)"]
88             redraw_windows = True
89         elif read_message_queue.parse_thingshere:
90             world_data["look"] += [message]
91         elif message[0:4] == "LOG ":
92             world_data["log"] += [message[4:]]
93             redraw_windows = True
94         elif message == "WORLD_UPDATED":
95             query_mapcell()
96         elif message[:6] == "PLUGIN":
97             str_plugin = message[7:]
98             if (str_plugin.replace("_", "").isalnum()
99                 and os.access("plugins/client/" + str_plugin + ".py",
100                     os.F_OK)):
101                 exec(open("plugins/client/" + str_plugin + ".py").read())
102                 return
103             raise SystemExit("Invalid plugin load path in message: " + message)
104 read_message_queue.parse_thingshere = False
105
106
107 def cursed_main(stdscr):
108     global redraw_windows
109
110     def ping_test():
111         half_wait_time = 5
112         if len(new_data_from_server) > 0:
113             ping_test.sent = False
114         elif ping_test.wait_start + half_wait_time < time.time():
115             if not ping_test.sent:
116                 io["file_out"].write("PING\n")
117                 io["file_out"].flush()
118                 ping_test.sent = True
119                 ping_test.wait_start = time.time()
120             elif ping_test.sent:
121                 raise SystemExit("Server not answering anymore.")
122     ping_test.wait_start = 0
123
124     def read_into_message_queue():
125         if new_data_from_server == "":
126             return
127         new_open_end = False
128         if new_data_from_server[-1] is not "\n":
129             new_open_end = True
130         new_messages = new_data_from_server.splitlines()
131         if message_queue["open_end"]:
132             message_queue["messages"][-1] += new_messages[0]
133             del new_messages[0]
134         message_queue["messages"] += new_messages
135         if new_open_end:
136             message_queue["open_end"] = True
137
138     def set_and_redraw_windows(*ignore):
139         set_windows()
140         draw_screen()
141
142     curses.noecho()
143     curses.curs_set(False)
144     signal.signal(signal.SIGWINCH, set_and_redraw_windows)
145     set_windows()
146     delay = 1
147     while True:
148         stdscr.timeout(int(delay))
149         if delay < 1000:
150             delay = delay * 1.1
151         if redraw_windows:
152             delay = 1
153             draw_screen()
154             redraw_windows = False
155         char = stdscr.getch()
156         if char >= 0:
157             char = chr(char)
158             if char in commands:
159                 if len(commands[char]) == 1 or not world_data["look_mode"]:
160                     commands[char][0]()
161                 else:
162                     commands[char][1]()
163                 redraw_windows = True
164         new_data_from_server = io["file_in"].read()
165         ping_test()
166         read_into_message_queue()
167         read_worldstate()
168         read_message_queue()
169
170
171 try:
172     if (not os.access(io["path_out"], os.F_OK)):
173         msg = "No server input file found at " + io["path_out"] + "."
174         raise SystemExit(msg)
175     io["file_out"] = open(io["path_out"], "a")
176     io["file_in"] = open(io["path_in"], "r")
177     curses.wrapper(cursed_main)
178 except SystemExit as exit:
179     print("ABORTING: " + exit.args[0])
180 except:
181     print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
182     raise
183 finally:
184     if "file_out" in io:
185         io["file_out"].close()
186     if "file_in" in io:
187         io["file_in"].close()