1 | # |
||
2 | # Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/] |
||
3 | # |
||
4 | # This file is part of Outer Space. |
||
5 | # |
||
6 | # Outer Space is free software; you can redistribute it and/or modify |
||
7 | # it under the terms of the GNU General Public License as published by |
||
8 | # the Free Software Foundation; either version 2 of the License, or |
||
9 | # (at your option) any later version. |
||
10 | # |
||
11 | # Outer Space is distributed in the hope that it will be useful, |
||
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
14 | # GNU General Public License for more details. |
||
15 | # |
||
16 | # You should have received a copy of the GNU General Public License |
||
17 | # along with Outer Space; if not, write to the Free Software |
||
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
||
19 | # |
||
20 | |||
21 | import os.path |
||
22 | |||
23 | from igeclient import IClient, IClientDB |
||
24 | from ige.ospace import Rules |
||
25 | import ige.ospace.Const as Const |
||
26 | from ige.IDataHolder import IDataHolder |
||
27 | import ige, gdata, osci, math, time |
||
28 | from ige import log |
||
29 | |||
30 | # module globals |
||
31 | cmdProxy = None |
||
32 | db = None |
||
33 | callbackObj = None |
||
34 | lastUpdate = -1 |
||
35 | server = None |
||
36 | serverVersion = None |
||
37 | ignoreMsgs = {} |
||
38 | nonexistingObj = {} |
||
39 | options = None |
||
40 | account = None |
||
41 | |||
42 | def initialize(aServer, aCallbackObj, anOptions): |
||
43 | global callbackObj, server, options |
||
44 | callbackObj = aCallbackObj |
||
45 | server = aServer |
||
46 | |||
47 | options = anOptions |
||
48 | initCmdProxy(options.heartbeat) |
||
49 | |||
50 | def reinitialize(): |
||
51 | global cmdProxy |
||
52 | cmdProxy = None |
||
53 | initCmdProxy(options.heartbeat) |
||
54 | |||
55 | def initCmdProxy(keepAliveTime): |
||
56 | global cmdProxy, server |
||
57 | if not cmdProxy: |
||
58 | callbackObj.onInitConnection() |
||
59 | proxy = None |
||
60 | if gdata.config.proxy.http != None: |
||
61 | proxy = gdata.config.proxy.http |
||
62 | cmdProxy = IClient.IClient(server, proxy, msgHandler, idleHandler, 'OSClient/%s' % ige.version.versionString, keepAliveTime) |
||
63 | callbackObj.onConnInitialized() |
||
64 | cmdProxy.connect() |
||
65 | |||
66 | ## Authentication |
||
67 | |||
68 | def login(gameid, login, password): |
||
69 | global account |
||
70 | if gdata.config.client.keepAlive != None: |
||
71 | cmdProxy.keepAliveTime = int(gdata.config.client.keepAlive) |
||
72 | if cmdProxy.login(gameid, login, password): |
||
73 | account = cmdProxy.getAccountData() |
||
74 | return 1 |
||
75 | return 0 |
||
76 | |||
77 | def createAccount(login, password, nick, email): |
||
78 | global cmdProxy, server |
||
79 | if not cmdProxy: |
||
80 | callbackObj.onInitConnection() |
||
81 | proxy = None |
||
82 | if gdata.config.proxy.http != None: |
||
83 | proxy = gdata.config.proxy.http |
||
84 | cmdProxy = IClient.IClient(server, proxy, msgHandler, idleHandler, 'OSClient/%d.%d.%d%s' % osci.version) |
||
85 | cmdProxy.connect(login) |
||
86 | if gdata.config.client.keepAlive != None: |
||
87 | cmdProxy.keepAliveTime = int(gdata.config.client.keepAlive) |
||
88 | callbackObj.onConnInitialized() |
||
89 | return cmdProxy.createAccount(login, password, nick, email) |
||
90 | |||
91 | def logout(): |
||
92 | global db, lastUpdate, account |
||
93 | if cmdProxy and cmdProxy.logged: |
||
94 | cmdProxy.logout() |
||
95 | saveDB() |
||
96 | db = None |
||
97 | account = None |
||
98 | lastUpdate = -1 |
||
99 | |||
100 | def saveDB(): |
||
101 | if db: |
||
102 | log.message('OSClient', 'Saving database') |
||
103 | db.save() |
||
104 | |||
105 | ## Message handler |
||
106 | |||
107 | def msgHandler(mid, data): |
||
108 | if ignoreMsgs.has_key(mid): |
||
109 | log.debug('OSClient', 'ignoring message', mid, data) |
||
110 | return |
||
111 | if mid == Const.SMESSAGE_NEWTURN: |
||
112 | updateDatabase() |
||
113 | elif mid == Const.SMESSAGE_NEWMESSAGE: |
||
114 | getMessages() |
||
115 | elif mid == IClient.MSG_CMD_BEGIN: |
||
116 | callbackObj.onCmdBegin() |
||
117 | elif mid == IClient.MSG_CMD_END: |
||
118 | callbackObj.onCmdEnd() |
||
119 | else: |
||
120 | log.debug('OSClient', 'unhandled message', mid, data) |
||
121 | |||
122 | def messageIgnore(mid): |
||
123 | global ignoreMsgs |
||
124 | ignoreMsgs[mid] = None |
||
125 | |||
126 | def messageEnable(mid): |
||
127 | global ignoreMsgs |
||
128 | if ignoreMsgs.has_key(mid): |
||
129 | del ignoreMsgs[mid] |
||
130 | |||
131 | ## Idle handler |
||
132 | def idleHandler(): |
||
133 | callbackObj.onWaitingForResponse() |
||
134 | |||
135 | ## Updater |
||
136 | |||
137 | def updateDatabase(clearDB = 0): |
||
138 | try: |
||
139 | return updateDatabaseUnsafe(clearDB) |
||
140 | except: |
||
141 | log.warning("Cannot update database") |
||
142 | # again with clear |
||
143 | callbackObj.onUpdateFinished() |
||
144 | messageEnable(Const.SMESSAGE_NEWTURN) |
||
145 | messageEnable(Const.SMESSAGE_NEWMESSAGE) |
||
146 | return updateDatabaseUnsafe(clearDB = 1, force = 1) |
||
147 | |||
148 | def updateDatabaseUnsafe(clearDB = 0, force = 0): |
||
149 | """Update database by fetching data from the server.""" |
||
150 | global lastUpdate, nonexistingObj, db |
||
151 | # get real turn |
||
152 | result = cmdProxy.getIntroInfo(Const.OID_UNIVERSE) |
||
153 | if not db: |
||
154 | dbLocation = os.path.join(options.configDir, 'player_data') |
||
155 | db = IClientDB.IClientDB(result.cid, result.turn, dbLocation, cmdProxy.gameID) |
||
156 | if clearDB: |
||
157 | db.clear() |
||
158 | db.turn = result.turn |
||
159 | # |
||
160 | if db.turn <= lastUpdate and not force: |
||
161 | return |
||
162 | log.message('IClient', 'Updating...') |
||
163 | lastUpdate = db.turn |
||
164 | nonexistingObj.clear() |
||
165 | # start updating... |
||
166 | messageIgnore(Const.SMESSAGE_NEWTURN) |
||
167 | messageIgnore(Const.SMESSAGE_NEWMESSAGE) |
||
168 | callbackObj.onUpdateStarting() |
||
169 | current = 0 |
||
170 | max = 1 |
||
171 | # compute total objects to be fetched |
||
172 | max += 6 # clear map, get messages, ... |
||
173 | current += 1 |
||
174 | callbackObj.onUpdateProgress(current, max, _("Deleting obsolete data...")) |
||
175 | # delete selected objects |
||
176 | # reset combatCounters |
||
177 | for objID in db.keys(): |
||
178 | obj = db[objID] |
||
179 | if hasattr(obj, "combatCounter"): |
||
180 | obj.combatCounter = 0 |
||
181 | View Code Duplication | if not hasattr(obj, 'type'): |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
182 | del db[objID] |
||
183 | elif obj.type == Const.T_FLEET: |
||
184 | del db[objID] |
||
185 | elif hasattr(obj, 'owner') and obj.owner == db.playerID \ |
||
186 | and objID != db.playerID: |
||
187 | # delete player's objects |
||
188 | del db[objID] |
||
189 | else: |
||
190 | if hasattr(obj, "scanPwr"): obj.scanPwr = 0 |
||
191 | if hasattr(obj, "scannerPwr"): obj.scannerPwr = 0 |
||
192 | # update player |
||
193 | current += 1 |
||
194 | callbackObj.onUpdateProgress(current, max, _("Downloading player data...")) |
||
195 | db[db.playerID] = get(db.playerID) |
||
196 | player = db[db.playerID] |
||
197 | # update from scanner's map |
||
198 | current += 1 |
||
199 | callbackObj.onUpdateProgress(current, max, _("Updating scanner...")) |
||
200 | map = cmdProxy.getScannerMap(db.playerID) |
||
201 | for objID in map: |
||
202 | db[objID] = map[objID] |
||
203 | # update player's planets and fleets |
||
204 | current += 1 |
||
205 | callbackObj.onUpdateProgress(current, max, _("Downloading planets and fleets data...")) |
||
206 | for obj in cmdProxy.multiGetInfo(1, player.planets[:] + player.fleets[:]): |
||
207 | db[obj.oid] = obj |
||
208 | # TODO: try to load allies's info |
||
209 | # get messages from server |
||
210 | current += 1 |
||
211 | callbackObj.onUpdateProgress(current, max, _("Downloading messages...")) |
||
212 | getMessages() |
||
213 | log.message('IClient', 'Update finished.') |
||
214 | callbackObj.onUpdateFinished() |
||
215 | messageEnable(Const.SMESSAGE_NEWTURN) |
||
216 | messageEnable(Const.SMESSAGE_NEWMESSAGE) |
||
217 | |||
218 | ## Basic functions |
||
219 | |||
220 | def keepAlive(force = False): |
||
221 | if cmdProxy: |
||
222 | try: |
||
223 | if force or cmdProxy.doKeepAlive(): |
||
224 | if db: |
||
225 | # client is logged into the game |
||
226 | getMessages() |
||
227 | else: |
||
228 | # client is in player selection / galaxy booking phase |
||
229 | cmdProxy.keepAlive() |
||
230 | except ige.NoAccountException: |
||
231 | pass |
||
232 | |||
233 | View Code Duplication | def get(objID, forceUpdate = 0, noUpdate = 0, canBePublic = 1, publicOnly = 0): |
|
0 ignored issues
–
show
|
|||
234 | global nonexistingObj |
||
235 | if nonexistingObj.has_key(objID) and not forceUpdate: |
||
236 | return None |
||
237 | if noUpdate: |
||
238 | return db.get(objID, None) |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
239 | if (db.needsUpdate(objID) or forceUpdate) and not publicOnly: |
||
240 | try: |
||
241 | db[objID] = cmdProxy.getInfo(objID) |
||
242 | except ige.SecurityException: |
||
243 | if canBePublic: |
||
244 | db[objID] = cmdProxy.getPublicInfo(objID) |
||
245 | else: |
||
246 | return db.get(objID, None) |
||
247 | except ige.NoSuchObjectException: |
||
248 | if db.has_key(objID): |
||
249 | del db[objID] |
||
250 | nonexistingObj[objID] = None |
||
251 | return None |
||
252 | if (db.needsUpdate(objID) or forceUpdate) and publicOnly: #for when the data you need is never anything but public |
||
253 | try: |
||
254 | db[objID] = cmdProxy.getPublicInfo(objID) |
||
255 | except ige.NoSuchObjectException: |
||
256 | if db.has_key(objID): |
||
257 | del db[objID] |
||
258 | nonexistingObj[objID] = None |
||
259 | return None |
||
260 | return db[objID] |
||
261 | |||
262 | def updateIDs(objIDs): |
||
263 | delete = objIDs[:] |
||
264 | for obj in cmdProxy.multiGetInfo(1, objIDs): |
||
265 | db[obj.oid] = obj |
||
266 | delete.remove(obj.oid) |
||
267 | for objID in delete: |
||
268 | if db.has_key(objID): |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
269 | del db[objID] |
||
270 | |||
271 | View Code Duplication | def getRelationTo(objID): |
|
0 ignored issues
–
show
|
|||
272 | obj = getPlayer() |
||
273 | if obj.oid == objID: |
||
274 | return Const.REL_UNITY |
||
275 | if objID == Const.OID_NONE: |
||
276 | return Const.REL_UNDEF |
||
277 | dipl = obj.diplomacyRels.get(objID, None) |
||
278 | if dipl: |
||
279 | return dipl.relation |
||
280 | else: |
||
281 | return obj.defaultRelation |
||
282 | |||
283 | def getTurn(): |
||
284 | return db.turn |
||
285 | |||
286 | def getFullTechInfo(techID): |
||
287 | player = db[db.playerID] |
||
288 | return Rules.techs[techID] |
||
289 | |||
290 | def getTechInfo(techID): |
||
291 | player = db[db.playerID] |
||
292 | tech = Rules.techs[techID] |
||
293 | # player possess this technology |
||
294 | if techID in player.techs or tech.fullInfo: |
||
295 | return tech |
||
296 | |||
297 | # player can research this technology |
||
298 | canResearch = 1 |
||
299 | if player.race not in tech.researchRaces: |
||
300 | canResearch = 0 |
||
301 | for tmpTechID, improvement in tech.researchRequires: |
||
302 | if tmpTechID not in player.techs or player.techs[tmpTechID] < improvement: |
||
303 | canResearch = 0 |
||
304 | break |
||
305 | for stratRes in tech.researchReqSRes: |
||
306 | if player.stratRes.get(stratRes, 0) < 1: |
||
307 | canResearch = 0 |
||
308 | break |
||
309 | for tmpTechID in player.techs: |
||
310 | if techID in Rules.techs[tmpTechID].researchDisables: |
||
311 | canResearch = 0 |
||
312 | break |
||
313 | if tech.level > player.techLevel: |
||
314 | canResearch = 0 |
||
315 | |||
316 | if canResearch: |
||
317 | result = IDataHolder() |
||
318 | result.partialData = None |
||
319 | for attr in ["id", 'name', 'isDiscovery', 'isStructure', |
||
320 | 'isProject', 'isShipEquip', 'isShipHull', 'researchMod', |
||
321 | 'researchTurns', 'textPreRsrch', 'researchRequires', 'subtype', |
||
322 | "researchReqSRes", "researchDisables", "level", "researchRaces"]: |
||
323 | setattr(result, attr, getattr(tech, attr)) |
||
324 | return result |
||
325 | # player should know only basic params about tech |
||
326 | result = IDataHolder() |
||
327 | result.partialData = None |
||
328 | for attr in ["id", "name", "researchRequires", "subtype", "level", "researchRaces"]: |
||
329 | setattr(result, attr, getattr(tech, attr)) |
||
330 | return result |
||
331 | |||
332 | def getAllTechIDs(): |
||
333 | return Rules.techs.keys() |
||
334 | |||
335 | def getPlayerID(): |
||
336 | return db.playerID |
||
337 | |||
338 | def getPlayer(): |
||
339 | return db[db.playerID] |
||
340 | |||
341 | View Code Duplication | def getDiplomacyWith(contactID): |
|
0 ignored issues
–
show
|
|||
342 | obj = getPlayer() |
||
343 | dipl = obj.diplomacyRels.get(contactID, None) |
||
344 | if not dipl: |
||
345 | # make default |
||
346 | dipl = IDataHolder() |
||
347 | dipl.type = Const.T_DIPLREL |
||
348 | dipl.pacts = { |
||
349 | Const.PACT_ALLOW_CIVILIAN_SHIPS: [Const.PACT_ACTIVE, Const.PACT_ALLOW_CIVILIAN_SHIPS] |
||
350 | } |
||
351 | dipl.relation = obj.defaultRelation |
||
352 | dipl.relChng = 0 |
||
353 | dipl.lastContact = 0 |
||
354 | dipl.stats = None |
||
355 | dipl.contactType = Const.CONTACT_NONE |
||
356 | obj.diplomacyRels[playerID] = dipl |
||
357 | return dipl |
||
358 | |||
359 | def getMessages(): |
||
360 | # construct list of mailboxes |
||
361 | mailboxes = [] |
||
362 | mailboxes.append((db.playerID, getMessagesLastID(db.playerID))) |
||
363 | galaxyID = getPlayer().galaxy |
||
364 | if galaxyID: |
||
365 | mailboxes.append((galaxyID, getMessagesLastID(galaxyID))) |
||
366 | mailboxes.append((Const.OID_UNIVERSE, getMessagesLastID(Const.OID_UNIVERSE))) |
||
367 | # get data |
||
368 | data = cmdProxy.multiGetMsgs(Const.OID_UNIVERSE, mailboxes) |
||
369 | # process |
||
370 | new = 0 |
||
371 | now = time.time() |
||
372 | for objID, messages in data: |
||
373 | obj = get(objID) |
||
374 | # delete old messages TODO leave this to the user |
||
375 | #for messageID in obj._messages.keys(): |
||
376 | # message = obj._messages[messageID] |
||
377 | # if message["time"] + Rules.messageTimeout < now: |
||
378 | # del obj._messages[messageID] |
||
379 | # add new |
||
380 | for message in messages: |
||
381 | #@log.debug("Got message ID", message["id"]) |
||
382 | if message["id"] not in obj._messages: |
||
383 | if message["forum"] != "OUTBOX": |
||
384 | message["readed"] = 0 |
||
385 | message["replied"] = 0 |
||
386 | else: |
||
387 | message["readed"] = 1 |
||
388 | message["replied"] = 0 |
||
389 | obj._messagesLastID = max(message["id"], obj._messagesLastID) |
||
390 | obj._messages[message["id"]] = message |
||
391 | new += 1 |
||
392 | else: |
||
393 | log.warning("Got duplicated message", message) |
||
394 | if new > 0: |
||
395 | callbackObj.onNewMessages(new) |
||
396 | return new |
||
397 | |||
398 | def getMessagesLastID(objID): |
||
399 | obj = get(objID, publicOnly = 1) |
||
400 | if not hasattr(obj, "_messages"): |
||
401 | log.debug("Creating _messages") |
||
402 | obj._messages = {} |
||
403 | obj._messagesLastID = -1 |
||
404 | return obj._messagesLastID |
||
405 |