Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2from __future__ import annotations 

3 

4# -- stdlib -- 

5from typing import List, Optional, Set, TYPE_CHECKING, Tuple 

6import logging 

7 

8# -- third party -- 

9from gevent import Greenlet 

10from mypy_extensions import TypedDict 

11 

12# -- own -- 

13from endpoint import Endpoint 

14from game.base import Packet 

15from server.base import Game 

16from server.endpoint import Client 

17from server.utils import command 

18from utils.misc import throttle 

19import wire 

20 

21 

22# -- code -- 

23if TYPE_CHECKING: 

24 from server.core import Core # noqa: F401 

25 

26 

27# -- code -- 

28log = logging.getLogger('Observe') 

29 

30 

31class ObserveAssocOnClient(TypedDict): 

32 obs: Set[Client] # observers 

33 reqs: Set[int] # observe requests 

34 ob: Optional[Client] # observing 

35 

36 

37def Au(self: Observe, u: Client) -> ObserveAssocOnClient: 

38 return u._[self] 

39 

40 

41class ObserveAssocOnGame(TypedDict): 

42 _notifier: Optional[Greenlet] 

43 

44 

45def Ag(self: Observe, g: Game) -> ObserveAssocOnGame: 

46 return g._[self] 

47 

48 

49class Observe(object): 

50 def __init__(self, core: Core): 

51 self.core = core 

52 

53 core.events.user_state_transition += self.handle_ust_observee 

54 core.events.game_created += self.handle_game_created 

55 core.events.game_joined += self.handle_game_joined 

56 core.events.game_data_send += self.handle_game_data_send 

57 

58 _ = core.events.client_command 

59 _[wire.Observe] += self._observe 

60 _[wire.GrantObserve] += self._grant 

61 _[wire.LeaveRoom] += self._leave 

62 _[wire.KickObserver] += self._kick 

63 

64 self._bigbrothers: List[int] = [] 

65 

66 def __repr__(self) -> str: 

67 return self.__class__.__name__ 

68 

69 def handle_ust_observee(self, ev: Tuple[Client, str, str]) -> Tuple[Client, str, str]: 

70 c, f, t = ev 

71 

72 if (f, t) == ('ready', 'game'): 

73 for u in Au(self, c)['obs']: 

74 self._observe_start(u, c) 

75 

76 elif f in ('room', 'ready', 'wait') and t == 'lobby': 

77 for u in list(Au(self, c)['obs']): 

78 self.observe_detach(u) 

79 

80 elif (f, t) == ('game', 'finishing'): 

81 for u in list(Au(self, c)['obs']): 

82 self._observe_end(u, c) 

83 

84 if t == 'lobby' or (f, t) == ('uninitialized', 'freeslot'): 

85 assoc: ObserveAssocOnClient = { 

86 'obs': set(), 

87 'reqs': set(), 

88 'ob': None, 

89 } 

90 c._[self] = assoc 

91 

92 if f in ('room', 'ready', 'game') or \ 

93 t in ('room', 'ready', 'game'): 

94 core = self.core 

95 # TODO: order with core.game? 

96 g = core.game.current(c) 

97 if g: self._notify(g) 

98 

99 return ev 

100 

101 def handle_game_created(self, g: Game) -> Game: 

102 assoc: ObserveAssocOnGame = { 

103 '_notifier': None 

104 } 

105 g._[self] = assoc 

106 return g 

107 

108 def handle_game_joined(self, ev: Tuple[Game, Client]) -> Tuple[Game, Client]: 

109 g, c = ev 

110 core = self.core 

111 for ob in Au(self, c)['obs']: 

112 ob.write(wire.GameJoined(core.view.GameDetail(g))) 

113 core.lobby.state_of(ob).transit('ob') 

114 

115 return ev 

116 

117 def handle_game_data_send(self, ev: Tuple[Game, Client, Packet]) -> Tuple[Game, Client, Packet]: 

118 core = self.core 

119 g, u, pkt = ev 

120 gid = core.room.gid_of(g) 

121 assert pkt is not None 

122 

123 obs = Au(self, u)['obs'] 

124 

125 if not obs: 

126 return ev 

127 

128 d = Endpoint.encode(wire.GameData( 

129 gid=gid, tag=pkt.tag, data=pkt.data 

130 )) 

131 

132 for u in obs: 

133 u.raw_write(d) 

134 

135 return ev 

136 

137 # ----- Client Commands ----- 

138 @command('lobby') 

139 def _observe(self, u: Client, ev: wire.Observe) -> None: 

140 core = self.core 

141 

142 observee = core.lobby.get(ev.uid) 

143 if observee is None: 143 ↛ 144line 143 didn't jump to line 144, because the condition on line 143 was never true

144 return 

145 

146 if core.lobby.state_of(observee) == 'ob': 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 observee = Au(self, observee)['ob'] 

148 assert observee 

149 

150 if core.lobby.state_of(observee) not in ('game', 'room', 'ready'): 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

151 return 

152 

153 uid = core.auth.uid_of(u) 

154 

155 if uid in self._bigbrothers: 

156 observee.write(wire.SystemMsg( 

157 '管理员对你使用了强制观战,效果拔群。' 

158 '强制观战功能仅用来处理纠纷,如果涉及滥用,请向 Proton 投诉。' 

159 )) 

160 self.observe_attach(u, observee) 

161 return 

162 

163 if uid in Au(self, observee)['reqs']: 

164 # request already sent 

165 return 

166 

167 Au(self, observee)['reqs'].add(uid) 

168 observee.write(wire.ObserveRequest(uid=uid)) 

169 

170 @command('room', 'ready', 'game') 

171 def _grant(self, c: Client, ev: wire.GrantObserve) -> None: 

172 if ev.uid not in Au(self, c)['reqs']: 

173 return 

174 

175 Au(self, c)['reqs'].remove(ev.uid) 

176 

177 core = self.core 

178 ob = core.lobby.get(ev.uid) 

179 

180 if ob is None: 180 ↛ 181line 180 didn't jump to line 181, because the condition on line 180 was never true

181 return 

182 

183 if core.lobby.state_of(ob) != 'lobby': 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true

184 return 

185 

186 if ev.grant: 

187 self.observe_attach(ob, c) 

188 else: 

189 ob.write(wire.Error('observe_refused')) 

190 

191 @command('room', 'ready', 'game') 

192 def _kick(self, c: Client, ev: wire.KickObserver) -> None: 

193 core = self.core 

194 ob = core.lobby.get(ev.uid) 

195 if not ob: 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true

196 return 

197 

198 g = core.game.current(c) 

199 if not g: return 199 ↛ exitline 199 didn't return from function '_kick', because the return on line 199 wasn't executed

200 

201 for u in core.room.online_users_of(g): 201 ↛ 205line 201 didn't jump to line 205, because the loop on line 201 didn't complete

202 if ob in Au(self, u)['obs']: 202 ↛ 201line 202 didn't jump to line 201, because the condition on line 202 was never false

203 break 

204 else: 

205 return 

206 

207 assert core.lobby.state_of(ob) == 'ob', (ob, core.lobby.state_of(ob)) 

208 

209 self.observe_detach(ob) 

210 return 

211 

212 # TODO 

213 ''' 

214 bl = self.ob_banlist[other] 

215 bl.add(c) 

216 

217 s = Client.encode(['ob_kick_request', [user, other, len(bl)]]) 

218 for cl in self.users: 

219 cl.raw_write(s) 

220 cl.observers and cl.observers.raw_write(s) 

221 

222 return len(bl) >= len(self.users) // 2 

223 

224 self.exit_game(other) 

225 ''' 

226 

227 @command('ob') 

228 def _leave(self, u: Client, ev: wire.LeaveRoom) -> None: 

229 self.observe_detach(u) 

230 

231 # ----- Public Methods ----- 

232 def add_bigbrother(self, uid: int) -> None: 

233 self._bigbrothers.append(uid) 

234 

235 def remove_bigbrother(self, uid: int) -> None: 

236 try: 

237 self._bigbrothers.remove(uid) 

238 except Exception: 

239 pass 

240 

241 def observe_attach(self, ob: Client, observee: Client) -> None: 

242 core = self.core 

243 

244 g = core.game.current(observee) 

245 if not g: 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true

246 return 

247 

248 users = core.room.online_users_of(g) 

249 

250 assert observee in users 

251 assert core.lobby.state_of(ob) == 'lobby' 

252 assert core.lobby.state_of(observee) in ('room', 'ready', 'game') 

253 

254 log.info("observe attach") 

255 

256 Au(self, observee)['obs'].add(ob) 

257 Au(self, ob)['ob'] = observee 

258 core.lobby.state_of(ob).transit('ob') 

259 

260 ob.write(wire.GameJoined(core.view.GameDetail(g))) 

261 

262 @core.runner.spawn 

263 def notify_observer() -> None: 

264 d = Endpoint.encode(wire.ObserverEnter( 

265 observer=core.auth.uid_of(ob), 

266 observee=core.auth.uid_of(observee), 

267 )) 

268 

269 for u in users: 

270 u.raw_write(d) 

271 for i in Au(self, u)['obs']: 

272 i.raw_write(d) 

273 

274 if core.room.is_started(g): 274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true

275 self._observe_start(ob, observee) 

276 core.game.replay(observee, to=ob) 

277 

278 def observe_detach(self, ob: Client) -> None: 

279 core = self.core 

280 assert core.lobby.state_of(ob) == 'ob' 

281 

282 observee = Au(self, ob)['ob'] 

283 if not observee: 283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true

284 return 

285 

286 Au(self, ob)['ob'] = None 

287 Au(self, observee)['obs'].remove(ob) 

288 

289 # TODO add these back 

290 # try: 

291 # del self.ob_banlist[user] 

292 # except KeyError: 

293 # pass 

294 

295 core.lobby.state_of(ob).transit('lobby') 

296 g = core.game.current(observee) 

297 

298 if not g: 298 ↛ 299line 298 didn't jump to line 299, because the condition on line 298 was never true

299 return 

300 

301 gid = core.room.gid_of(g) 

302 ob.write(wire.GameLeft(gid)) 

303 

304 @core.runner.spawn 

305 def notify_observer_leave() -> None: 

306 assert observee 

307 g = core.game.current(observee) 

308 if not g: 

309 return 

310 

311 ul = core.room.online_users_of(g) 

312 

313 d = Endpoint.encode(wire.ObserverLeave( 

314 observer=core.auth.uid_of(ob), 

315 observee=core.auth.uid_of(observee), 

316 )) 

317 

318 for u in ul: 

319 u.raw_write(d) 

320 for i in Au(self, u)['obs']: 320 ↛ 321line 320 didn't jump to line 321, because the loop on line 320 never started

321 i.raw_write(d) 

322 

323 # ----- Methods ----- 

324 def _observe_start(self, ob: Client, observee: Client) -> None: 

325 core = self.core 

326 uid = core.auth.uid_of(observee) 

327 g = core.game.current(observee) 

328 assert g 

329 

330 ob.write(wire.ObserveStarted(core.view.GameDetail(g), observee=uid)) 

331 core.lobby.state_of(ob).transit('ob') 

332 

333 def _observe_end(self, ob: Client, observee: Client) -> None: 

334 core = self.core 

335 g = core.game.current(observee) 

336 if not g: return 336 ↛ exitline 336 didn't return from function '_observe_end', because the return on line 336 wasn't executed

337 gid = core.room.gid_of(g) 

338 ob.write(wire.GameEnded(gid)) 

339 core.lobby.state_of(ob).transit('ob') 

340 

341 def _notify(self, g: Game) -> None: 

342 notifier = Ag(self, g)['_notifier'] 

343 core = self.core 

344 

345 if notifier: 

346 notifier() 

347 return 

348 

349 @throttle(0.5) 

350 def _notifier() -> None: 

351 pl = core.room.users_of(g) 

352 obs: List[Client] = [] 

353 for u in pl: 

354 obs.extend(Au(self, u)['obs']) 

355 

356 core.runner.spawn(core.room.send_room_users, g, obs) 

357 

358 Ag(self, g)['_notifier'] = _notifier 

359 

360 _notifier()