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'): |
|
|
|
|
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): |
|
|
|
|
234
|
|
|
global nonexistingObj |
235
|
|
|
if nonexistingObj.has_key(objID) and not forceUpdate: |
236
|
|
|
return None |
237
|
|
|
if noUpdate: |
238
|
|
|
return db.get(objID, None) |
|
|
|
|
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): |
|
|
|
|
269
|
|
|
del db[objID] |
270
|
|
|
|
271
|
|
View Code Duplication |
def getRelationTo(objID): |
|
|
|
|
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): |
|
|
|
|
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
|
|
|
|