|
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 sys, copy |
|
22
|
|
|
from ige import log |
|
23
|
|
|
from ige.ospace import Const |
|
24
|
|
|
import ige.ospace.Rules |
|
25
|
|
|
import hashlib, os.path |
|
26
|
|
|
import cPickle as pickle |
|
27
|
|
|
import types |
|
28
|
|
|
from ige.ospace import TechHandlers |
|
29
|
|
|
from xml.sax.handler import ContentHandler |
|
30
|
|
|
from ige.IDataHolder import IDataHolder |
|
31
|
|
|
import xml.sax |
|
32
|
|
|
import copy |
|
33
|
|
|
|
|
34
|
|
|
def noop(*args, **kwargs): |
|
35
|
|
|
return 1 |
|
36
|
|
|
|
|
37
|
|
|
attrs = { |
|
38
|
|
|
'id': 0, |
|
39
|
|
|
# types of technologies |
|
40
|
|
|
'isDiscovery': 0, |
|
41
|
|
|
'isStructure': 0, |
|
42
|
|
|
'isProject': 0, |
|
43
|
|
|
'isShipEquip': 0, |
|
44
|
|
|
'isShipHull': 0, |
|
45
|
|
|
'isMine': 0, |
|
46
|
|
|
'isStarting': 0, |
|
47
|
|
|
"subtype": "techSubtype", |
|
48
|
|
|
"level": 0, |
|
49
|
|
|
"maxImprovement": 5, # Rules.techMaxImprovement, |
|
50
|
|
|
"isMilitary": 0, |
|
51
|
|
|
"globalDisabled": 0, # to disallow some projects in global queues |
|
52
|
|
|
# dialog info |
|
53
|
|
|
'shortname': '', # for TechInfoDlg tech linkages |
|
54
|
|
|
# construction costs & conditions |
|
55
|
|
|
'buildProd': 0, |
|
56
|
|
|
'buildTurns': 1, |
|
57
|
|
|
'buildSRes': {'resource': 'amount'}, |
|
58
|
|
|
# operation costs |
|
59
|
|
|
'operBio': 0, |
|
60
|
|
|
'operEn': 0, |
|
61
|
|
|
"operWorkers": 0, |
|
62
|
|
|
# production |
|
63
|
|
|
'prodBio': 0 # bio |
|
64
|
|
|
'prodEn': 0 # energy |
|
65
|
|
|
'prodProd': 0 # production |
|
66
|
|
|
'prodSci': 0 # science |
|
67
|
|
|
'prodEnv': 0 # enviromental effect |
|
68
|
|
|
'prodPop': 0 # produce population |
|
69
|
|
|
'prodBioMod': [0.0, 0.0, 0.0, 0.0] # tuple of (plBio, plMin, plEn, default) |
|
70
|
|
|
'prodEnMod': [0.0, 0.0, 0.0, 0.0] # dtto |
|
71
|
|
|
'prodProdMod': [0.0, 0.0, 0.0, 0.0] # dtto |
|
72
|
|
|
'prodSciMod': [0.0, 0.0, 0.0, 1.0] # ditto; default is 1.0 for legacy support |
|
73
|
|
|
# env |
|
74
|
|
|
'solarMod': 0 # -: cloak solar radiation / +: create thermal radiation |
|
75
|
|
|
# storage |
|
76
|
|
|
'storBio': 0, |
|
77
|
|
|
'storProd': 0, |
|
78
|
|
|
'storEn': 0, |
|
79
|
|
|
'storPop': 0, |
|
80
|
|
|
# morale affecting |
|
81
|
|
|
'revoltThr': 0, |
|
82
|
|
|
'moraleTrgt': 0, |
|
83
|
|
|
'govPwr': 0, |
|
84
|
|
|
# military |
|
85
|
|
|
'scannerPwr': 0, |
|
86
|
|
|
"structWeapons": [0], |
|
87
|
|
|
"planetShield": 0 # planetary shield; when structure built, shield = 0; shield will regenerate at 2% per turn until equal to this value. Structures do not add shield strength; strongest shield = planet shield |
|
88
|
|
|
"systemAtt": 0, |
|
89
|
|
|
"systemDef": 0, |
|
90
|
|
|
"refuelMax": 0, |
|
91
|
|
|
"refuelInc": 0, |
|
92
|
|
|
"repairShip": 0.0, |
|
93
|
|
|
"upgradeShip": 0, |
|
94
|
|
|
"trainShipInc": 0.0 # how many exp/turn |
|
95
|
|
|
"trainShipMax": 0 # exp. cap (in base exps), not affected by techEff |
|
96
|
|
|
"fleetSpeedBoost": 0.0 # speed boost for stargates |
|
97
|
|
|
# misc |
|
98
|
|
|
"unpackPop": 0, |
|
99
|
|
|
'envDmg': 0, |
|
100
|
|
|
'maxHP': 0, |
|
101
|
|
|
"fullInfo": 0 # if True, show full tech info even player not own tech |
|
102
|
|
|
# ship equipment |
|
103
|
|
|
'equipType': '' # identifier of subtype seq_mod's equipment type; see maxEquipType in Rules/__init__ |
|
104
|
|
|
"addMP": 0 # for extra MP to be added to ship equipment |
|
105
|
|
|
'combatClass': 0, |
|
106
|
|
|
'combatAtt': 0 # not cumulative for equipment; cumulative for hull, drives, cockpits, etc |
|
107
|
|
|
'combatDef': 0 # not cumulative for equipment; cumulative for hull, drives, cockpits, etc |
|
108
|
|
|
"missileDef": 0 # not cumulative for equipment; cumulative for hull, drives, cockpits, etc |
|
109
|
|
|
"combatAttPerc": 1.0 # multiplier of ATT; min of 100%; not cumulative |
|
110
|
|
|
"combatDefPerc": 1.0 # multiplier of DEF; min of 100%; not cumulative |
|
111
|
|
|
"missileDefPerc": 1.0 # multiplier of missile DEF; min of 100%; not cumulative |
|
112
|
|
|
'unpackStruct': '', |
|
113
|
|
|
'deployHandlerID': '' # technology ID of tech to find deployHandlerFunction & deployHandlerValidator (this can be the deployable device OR a project) |
|
114
|
|
|
'deployHandlerFunction': noop # function name of TechHandler |
|
115
|
|
|
'deployHandlerValidator': noop # function name of TechHandler Validator |
|
116
|
|
|
'signature': 0 # **** NOT cumulative (change effective 0.5.63) |
|
117
|
|
|
'signatureCloak': 1.0 # max of 1.0 is effective; not cumulative |
|
118
|
|
|
'signatureDecloak': 1.0 # min of 1.0 is effective; not cumulative |
|
119
|
|
|
"minSignature": 0, |
|
120
|
|
|
"slots": 0, |
|
121
|
|
|
"weight": 0, |
|
122
|
|
|
"maxWeight": 0, |
|
123
|
|
|
"engPwr": 0, |
|
124
|
|
|
"engStlPwr": 0, |
|
125
|
|
|
"shieldPerc": 0.0 # how many percent of maxHP have shields |
|
126
|
|
|
"minHull": 0, |
|
127
|
|
|
"maxHull": 10 # just make this higher than the largest hull so we know it doesn't break anything |
|
128
|
|
|
"maxInstallations": 0, |
|
129
|
|
|
"shieldRechargeFix": 0 # fixed amount of HP/turn to recharge |
|
130
|
|
|
"shieldRechargePerc": 0.0 # how many percent of shieldHP/turn is recharged |
|
131
|
|
|
"hardShield": 0.0 # shield penetrating weapons will penetrate at 100%; use as 1-hardShield for penentration level (hardShield percent = %damage absorbed by shield) |
|
132
|
|
|
"autoRepairFix": 0 # fixed amount of HP/turn to repair |
|
133
|
|
|
"autoRepairPerc": 0.0 # how many percent of maxHP/turn is repaired |
|
134
|
|
|
"damageAbsorb": 0 # amount of damage absorbed by the hull (not shield!); max sum is 5 damage (set in Rules) |
|
135
|
|
|
# weapons |
|
136
|
|
|
'weaponDmgMin': 0, |
|
137
|
|
|
'weaponDmgMax': 0, |
|
138
|
|
|
'weaponAtt': 0, |
|
139
|
|
|
'weaponClass': 0, |
|
140
|
|
|
"weaponROF": 0.0, |
|
141
|
|
|
"weaponIgnoreShield": 0, |
|
142
|
|
|
"weaponIsMissile": 0, |
|
143
|
|
|
"weaponGoodForFlak": 1, |
|
144
|
|
|
# mines |
|
145
|
|
|
'mineclass':0 # tech id of the mine; usually level 99 tech - structure in the system with the highest tech id will always deploy; others will be ignored (per player) |
|
146
|
|
|
'minenum':0 # number of mines this control structure supports; if another structure built more mines, mines will not self destruct |
|
147
|
|
|
'minerate':0 # number of turns between mine deployments; note that system will deploy mines on: turn%minerate==0 |
|
148
|
|
|
# research |
|
149
|
|
|
'researchRequires': ['technology'], |
|
150
|
|
|
'researchEnables': ['technology'], |
|
151
|
|
|
'researchDisables': ['technology'], |
|
152
|
|
|
'researchReqSRes': ['resource'], |
|
153
|
|
|
"researchMod": "expr", |
|
154
|
|
|
'researchTurns': 1, |
|
155
|
|
|
"researchRaces": "BCH", |
|
156
|
|
|
# misc |
|
157
|
|
|
"data": "none", |
|
158
|
|
|
"recheckWhenTechLost": 0, |
|
159
|
|
|
"deprecated": 0 # this tech is no longer active |
|
160
|
|
|
# before build handler |
|
161
|
|
|
'validateConstrHandler': noop, |
|
162
|
|
|
# after build handler |
|
163
|
|
|
'finishConstrHandler': noop, |
|
164
|
|
|
# after research handler |
|
165
|
|
|
'finishResearchHandler': noop, |
|
166
|
|
|
# names |
|
167
|
|
|
'name': u'Unspecified', |
|
168
|
|
|
# textual description |
|
169
|
|
|
'textPreRsrch': u'Not specified', |
|
170
|
|
|
'textDescr': u'Not specified', |
|
171
|
|
|
'textFlavor': u'Not specified', |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
# class representing technologies |
|
175
|
|
|
class Technology: |
|
176
|
|
|
|
|
177
|
|
|
def __init__(self, id, symbol, reg): |
|
178
|
|
|
self.id = id |
|
179
|
|
|
self.symbol = symbol |
|
180
|
|
|
if id in reg: |
|
181
|
|
|
raise KeyError("%s is already registered" % id) |
|
182
|
|
|
reg[id] = self |
|
183
|
|
|
|
|
184
|
|
|
def set(self, key, value): |
|
185
|
|
|
if attrs.has_key(key): |
|
186
|
|
|
attrType = type(attrs[key]) |
|
187
|
|
|
if attrType == types.IntType: |
|
188
|
|
|
value = int(value) |
|
189
|
|
|
elif attrType == types.FloatType: |
|
190
|
|
|
value = float(value) |
|
191
|
|
|
elif attrType == types.UnicodeType: |
|
192
|
|
|
pass |
|
193
|
|
|
elif attrType == types.StringType: |
|
194
|
|
|
value = str(value) |
|
195
|
|
|
elif attrType == types.FunctionType: |
|
196
|
|
|
value = getattr(TechHandlers, value) |
|
197
|
|
|
elif attrType == types.ListType: |
|
198
|
|
|
itemType = type(attrs[key][0]) |
|
199
|
|
|
if itemType == types.IntType: |
|
200
|
|
|
convertFunc = int |
|
201
|
|
|
elif itemType == types.StringType: |
|
202
|
|
|
convertFunc = str |
|
203
|
|
|
elif itemType == types.FloatType: |
|
204
|
|
|
convertFunc = float |
|
205
|
|
|
else: |
|
206
|
|
|
raise 'Unsupported attribute type %s' % repr(itemType) |
|
207
|
|
|
result = [] |
|
208
|
|
|
for item in value.split(','): |
|
209
|
|
|
if item: |
|
210
|
|
|
result.append(convertFunc(item)) |
|
211
|
|
|
value = result |
|
212
|
|
|
elif attrType == types.DictType: |
|
213
|
|
|
# format is key:value,key2:value2 |
|
214
|
|
|
dict_key, dict_value = copy.copy(attrs[key]).popitem() |
|
215
|
|
|
keyType = type(dict_key) |
|
216
|
|
|
if keyType == types.IntType: |
|
217
|
|
|
convertFuncKey = int |
|
218
|
|
|
elif keyType == types.StringType: |
|
219
|
|
|
convertFuncKey = str |
|
220
|
|
|
elif keyType == types.FloatType: |
|
221
|
|
|
convertFuncKey = float |
|
222
|
|
|
else: |
|
223
|
|
|
raise 'Unsupported attribute type %s' % repr(keyType) |
|
224
|
|
|
valueType = type(dict_value) |
|
225
|
|
|
if valueType == types.IntType: |
|
226
|
|
|
convertFuncValue = int |
|
227
|
|
|
elif valueType == types.StringType: |
|
228
|
|
|
convertFuncValue = str |
|
229
|
|
|
elif valueType == types.FloatType: |
|
230
|
|
|
convertFuncValue = float |
|
231
|
|
|
else: |
|
232
|
|
|
raise 'Unsupported attribute type %s' % repr(valueType) |
|
233
|
|
|
# let's parse! |
|
234
|
|
|
result = {} |
|
235
|
|
|
for pair in value.split(','): |
|
236
|
|
|
pair_key, pair_value = pair.split(':') |
|
237
|
|
|
pair_key = convertFuncKey(pair_key) |
|
238
|
|
|
pair_value = convertFuncValue(pair_value) |
|
239
|
|
|
result[pair_key] = pair_value |
|
240
|
|
|
value = result |
|
241
|
|
|
else: |
|
242
|
|
|
raise 'Unsupported attribute type %s' % repr(attrType) |
|
243
|
|
|
setattr(self, key, value) |
|
244
|
|
|
else: |
|
245
|
|
|
raise AttributeError('Cannot create %s - unsupported attribute.' % key) |
|
246
|
|
|
|
|
247
|
|
|
def __getattr__(self, attr): |
|
248
|
|
|
if attrs.has_key(attr): |
|
249
|
|
|
# optimalization |
|
250
|
|
|
setattr(self, attr, attrs[attr]) |
|
251
|
|
|
return attrs[attr] |
|
252
|
|
|
else: |
|
253
|
|
|
raise AttributeError('No attribute %s' % attr) |
|
254
|
|
|
|
|
255
|
|
|
def isDefault(self, attr): |
|
256
|
|
|
if hasattr(self, attr): |
|
257
|
|
|
return getattr(self, attr) == attrs[attr] |
|
258
|
|
|
else: |
|
259
|
|
|
return 1 |
|
260
|
|
|
|
|
261
|
|
|
def __repr__(self): |
|
262
|
|
|
result = '(Technology ' |
|
263
|
|
|
for key, value in self.__dict__.items(): |
|
264
|
|
|
result += '%s : %s, ' % (repr(key), repr(value)) |
|
265
|
|
|
result += ')' |
|
266
|
|
|
return result |
|
267
|
|
|
|
|
268
|
|
|
# holder for all technologies |
|
269
|
|
|
techs = {} |
|
270
|
|
|
|
|
271
|
|
|
# parse TechTree.xml and create all tech objects |
|
272
|
|
|
class TechTreeContentHandler(ContentHandler): |
|
273
|
|
|
def startDocument(self): |
|
274
|
|
|
self.state = 1 |
|
275
|
|
|
self.text = '' |
|
276
|
|
|
|
|
277
|
|
|
def endDocument(self): |
|
278
|
|
|
if self.state != 1: |
|
279
|
|
|
raise 'Wrong TechTree specification' |
|
280
|
|
|
|
|
281
|
|
|
def startElement(self, name, attrs): |
|
282
|
|
|
if self.state == 1 and name == 'techtree': |
|
283
|
|
|
self.state = 2 |
|
284
|
|
|
elif self.state == 2 and name == 'technology': |
|
285
|
|
|
log.debug('Tech %s [%s]' % (attrs['name'], attrs['id'])) |
|
286
|
|
|
self.state = 3 |
|
287
|
|
|
self.tech = Technology(int(attrs['id']), attrs['symbol'], techs) |
|
288
|
|
|
setattr(Tech, attrs['symbol'], int(attrs['id'])) |
|
289
|
|
|
self.tech.set('name', attrs['name']) |
|
290
|
|
|
elif self.state == 3 and name == 'structure': |
|
291
|
|
|
self.tech.set('isStructure', 1) |
|
292
|
|
|
for key in attrs.keys(): |
|
293
|
|
|
self.tech.set(key, attrs[key]) |
|
294
|
|
|
realBuildProd = int(self.tech.buildProd * ige.ospace.Rules.structDefaultCpCosts) |
|
295
|
|
|
self.tech.set('buildProd', realBuildProd) |
|
296
|
|
|
elif self.state == 3 and name == 'discovery': |
|
297
|
|
|
self.tech.set('isDiscovery', 1) |
|
298
|
|
|
for key in attrs.keys(): |
|
299
|
|
|
self.tech.set(key, attrs[key]) |
|
300
|
|
|
elif self.state == 3 and name == 'notdiscovery': |
|
301
|
|
|
self.tech.set('isDiscovery', 0) |
|
302
|
|
|
for key in attrs.keys(): |
|
303
|
|
|
self.tech.set(key, attrs[key]) |
|
304
|
|
|
elif self.state == 3 and name == 'starting': |
|
305
|
|
|
self.tech.set('isStarting', 1) |
|
306
|
|
|
for key in attrs.keys(): |
|
307
|
|
|
self.tech.set(key, attrs[key]) |
|
308
|
|
|
elif self.state == 3 and name == 'notstarting': |
|
309
|
|
|
self.tech.set('isStarting', 0) |
|
310
|
|
|
for key in attrs.keys(): |
|
311
|
|
|
self.tech.set(key, attrs[key]) |
|
312
|
|
|
elif self.state == 3 and name == 'shipequip': |
|
313
|
|
|
self.tech.set('isShipEquip', 1) |
|
314
|
|
|
for key in attrs.keys(): |
|
315
|
|
|
self.tech.set(key, attrs[key]) |
|
316
|
|
|
elif self.state == 3 and name == 'project': |
|
317
|
|
|
self.tech.set('isProject', 1) |
|
318
|
|
|
for key in attrs.keys(): |
|
319
|
|
|
self.tech.set(key, attrs[key]) |
|
320
|
|
|
elif self.state == 3 and name == 'shiphull': |
|
321
|
|
|
self.tech.set('isShipHull', 1) |
|
322
|
|
|
for key in attrs.keys(): |
|
323
|
|
|
self.tech.set(key, attrs[key]) |
|
324
|
|
|
elif self.state == 3 and name == 'mine': |
|
325
|
|
|
self.tech.set('isMine', 1) |
|
326
|
|
|
for key in attrs.keys(): |
|
327
|
|
|
self.tech.set(key, attrs[key]) |
|
328
|
|
|
elif self.state == 3 and name == 'data': |
|
329
|
|
|
for key in attrs.keys(): |
|
330
|
|
|
self.tech.set(key, attrs[key]) |
|
331
|
|
|
elif self.state == 3 and name == 'preresearch': |
|
332
|
|
|
self.state = 4 |
|
333
|
|
|
self.text = '' |
|
334
|
|
|
elif self.state == 3 and name == 'description': |
|
335
|
|
|
self.state = 4 |
|
336
|
|
|
self.text = '' |
|
337
|
|
|
elif self.state == 3 and name == 'flavor': |
|
338
|
|
|
self.state = 4 |
|
339
|
|
|
self.text = '' |
|
340
|
|
|
else: |
|
341
|
|
|
raise 'Unsupported tag %s' % str(name) |
|
342
|
|
|
|
|
343
|
|
|
def endElement(self, name): |
|
344
|
|
|
if self.state == 2 and name == 'techtree': |
|
345
|
|
|
self.state = 1 |
|
346
|
|
|
elif self.state == 3 and name == 'technology': |
|
347
|
|
|
self.state = 2 |
|
348
|
|
|
elif self.state == 4 and name == 'preresearch': |
|
349
|
|
|
self.tech.textPreRsrch = self.text |
|
350
|
|
|
self.state = 3 |
|
351
|
|
|
elif self.state == 4 and name == 'description': |
|
352
|
|
|
self.tech.textDescr = self.text |
|
353
|
|
|
self.state = 3 |
|
354
|
|
|
elif self.state == 4 and name == 'flavor': |
|
355
|
|
|
self.tech.textFlavor = self.text |
|
356
|
|
|
self.state = 3 |
|
357
|
|
|
|
|
358
|
|
|
def characters(self, text): |
|
359
|
|
|
self.text += text |
|
360
|
|
|
|
|
361
|
|
|
Tech = IDataHolder() |
|
362
|
|
|
|
|
363
|
|
|
## init is wrapping all code that needs to be performed before module |
|
364
|
|
|
# is ready. That is checking if techs changed, if not loading pickled |
|
365
|
|
|
# data, othervise rebuild database from source data |
|
366
|
|
|
# We should be able to do server-provided techs in the future (per galaxy |
|
367
|
|
|
# rulesets) |
|
368
|
|
|
|
|
369
|
|
|
|
|
370
|
|
|
def init(configDir): |
|
371
|
|
|
global Tech, techs |
|
372
|
|
|
## check, if anything has been changed |
|
373
|
|
|
|
|
374
|
|
|
def chsumDir(chsum, dirname, names): |
|
375
|
|
|
names.sort() |
|
376
|
|
|
for filename in names: |
|
377
|
|
|
if os.path.splitext(filename)[1] in ('.xml', '.py'): |
|
378
|
|
|
log.debug('Checking file', filename) |
|
379
|
|
|
fh = open(os.path.join(dirname, filename), 'rb') |
|
380
|
|
|
chsum.update(fh.read()) |
|
381
|
|
|
fh.close() |
|
382
|
|
|
|
|
383
|
|
|
# compute checksum |
|
384
|
|
|
moduleFile = sys.modules['ige.ospace.Rules'].__file__ |
|
385
|
|
|
forceLoad = 0 |
|
386
|
|
|
|
|
387
|
|
|
# regular module |
|
388
|
|
|
moduleDirectory = os.path.dirname(moduleFile) |
|
389
|
|
|
chsum = hashlib.sha1() |
|
390
|
|
|
os.path.walk(moduleDirectory, chsumDir, chsum) |
|
391
|
|
|
|
|
392
|
|
|
# read old checksum |
|
393
|
|
|
try: |
|
394
|
|
|
fh = open(os.path.join(configDir, 'checksum'), 'rb') |
|
395
|
|
|
oldChsum = fh.read() |
|
396
|
|
|
fh.close() |
|
397
|
|
|
except IOError: |
|
398
|
|
|
oldChsum = '' |
|
399
|
|
|
|
|
400
|
|
|
# compare |
|
401
|
|
|
if forceLoad or chsum.hexdigest() == oldChsum: |
|
402
|
|
|
# load old definitions |
|
403
|
|
|
log.message('Loading stored specifications from', configDir) |
|
404
|
|
|
techs = pickle.load(open(os.path.join(configDir, 'techs.spf'), 'rb')) |
|
405
|
|
|
Tech = pickle.load(open(os.path.join(configDir, 'Tech.spf'), 'rb')) |
|
406
|
|
|
|
|
407
|
|
|
log.message("There is %d technologies" % len(techs)) |
|
408
|
|
|
|
|
409
|
|
|
# clean up 'type' in lists |
|
410
|
|
|
for key in attrs.keys(): |
|
411
|
|
|
if type(attrs[key]) == types.ListType and len(attrs[key]) == 1: |
|
412
|
|
|
log.debug("Cleaning up", key) |
|
413
|
|
|
attrs[key] = [] |
|
414
|
|
|
|
|
415
|
|
|
else: |
|
416
|
|
|
# create new ones |
|
417
|
|
|
## load technologies definitions |
|
418
|
|
|
|
|
419
|
|
|
def processDir(arg, dirname, names): |
|
420
|
|
|
log.message('Loading XML files from', dirname) |
|
421
|
|
|
names.sort() |
|
422
|
|
|
for filename in names: |
|
423
|
|
|
if os.path.splitext(filename)[1] == '.xml': |
|
424
|
|
|
log.message('Parsing XML file', filename) |
|
425
|
|
|
xml.sax.parse(os.path.join(dirname, filename), TechTreeContentHandler()) |
|
426
|
|
|
|
|
427
|
|
|
# collect xml files |
|
428
|
|
|
os.path.walk(moduleDirectory, processDir, None) |
|
429
|
|
|
|
|
430
|
|
|
# clean up 'type' in lists |
|
431
|
|
|
for key in attrs.keys(): |
|
432
|
|
|
if type(attrs[key]) == types.ListType and len(attrs[key]) == 1: |
|
433
|
|
|
log.debug("Cleaning up", key) |
|
434
|
|
|
attrs[key] = [] |
|
435
|
|
|
elif type(attrs[key]) == types.DictType and len(attrs[key]) == 1: |
|
436
|
|
|
log.debug("Cleaning up", key) |
|
437
|
|
|
attrs[key] = {} |
|
438
|
|
|
|
|
439
|
|
|
|
|
440
|
|
|
# link tech tree using researchRequires fields |
|
441
|
|
|
# construct researchEnables fields |
|
442
|
|
|
log.message('Converting symbolic fields...') |
|
443
|
|
|
for techID in techs.keys(): |
|
444
|
|
|
tech = techs[techID] |
|
445
|
|
|
# convert symbolic names to numbers |
|
446
|
|
|
techIDs = [] |
|
447
|
|
|
for techSymName in tech.researchRequires: |
|
448
|
|
|
symName, improvement = techSymName.split('-') |
|
449
|
|
|
techIDs.append((getattr(Tech, symName), int(improvement))) |
|
450
|
|
|
tech.researchRequires = techIDs |
|
451
|
|
|
techIDs = {1: [], 2:[], 3:[], 4:[], 5:[], 6:[]} |
|
452
|
|
|
for techSymName in tech.researchEnables: |
|
453
|
|
|
improvement, symName = techSymName.split('-') |
|
454
|
|
|
techIDs[int(improvement)].append(getattr(Tech, symName)) |
|
455
|
|
|
tech.researchEnables = techIDs |
|
456
|
|
|
techIDs = [] |
|
457
|
|
|
for techSymName in tech.researchDisables: |
|
458
|
|
|
techIDs.append(getattr(Tech, techSymName)) |
|
459
|
|
|
tech.researchDisables = techIDs |
|
460
|
|
|
techIDs = [] |
|
461
|
|
|
if tech.unpackStruct: |
|
462
|
|
|
tech.unpackStruct = getattr(Tech, tech.unpackStruct) |
|
463
|
|
|
else: |
|
464
|
|
|
tech.unpackStruct = 0 |
|
465
|
|
|
|
|
466
|
|
|
# strat. resources |
|
467
|
|
|
# this has to be here to prevent import deadlock in Rules |
|
468
|
|
|
from ige.ospace.Rules import stratResAmountBig |
|
469
|
|
|
from ige.ospace.Rules import stratResAmountSmall |
|
470
|
|
|
|
|
471
|
|
|
stratRes = [] |
|
472
|
|
|
for sr in tech.researchReqSRes: |
|
473
|
|
|
stratRes.append(getattr(Const, sr)) |
|
474
|
|
|
tech.researchReqSRes = stratRes |
|
475
|
|
|
# build requirements with quantities |
|
476
|
|
|
stratRes = {} |
|
477
|
|
|
for resource in tech.buildSRes: |
|
478
|
|
|
resource_id = getattr(Const, resource) |
|
479
|
|
|
# prescription contains constants for big and small amounts |
|
480
|
|
|
# possibly within expression |
|
481
|
|
|
resource_amount = eval(tech.buildSRes[resource]) |
|
482
|
|
|
stratRes[resource_id] = resource_amount |
|
483
|
|
|
tech.buildSRes = stratRes |
|
484
|
|
|
|
|
485
|
|
|
# evaluate researchMod |
|
486
|
|
|
if tech.researchMod == "expr": |
|
487
|
|
|
tech.researchMod = 1.0 |
|
488
|
|
|
else: |
|
489
|
|
|
tech.researchMod = eval(tech.researchMod) |
|
490
|
|
|
|
|
491
|
|
|
# link |
|
492
|
|
|
log.message('Linking tech tree...') |
|
493
|
|
|
for techID in techs.keys(): |
|
494
|
|
|
tech = techs[techID] |
|
495
|
|
|
for tmpTechID, improvement in tech.researchRequires: |
|
496
|
|
|
if techID not in techs[tmpTechID].researchEnables[improvement]: |
|
497
|
|
|
techs[tmpTechID].researchEnables[improvement].append(techID) |
|
498
|
|
|
for improvement in tech.researchEnables.keys(): |
|
499
|
|
|
for tmpTechID in tech.researchEnables[improvement]: |
|
500
|
|
|
if (techID, improvement) not in techs[tmpTechID].researchRequires: |
|
501
|
|
|
techs[tmpTechID].researchRequires.append((techID, improvement)) |
|
502
|
|
|
|
|
503
|
|
|
changed = 1 |
|
504
|
|
|
while changed: |
|
505
|
|
|
changed = 0 |
|
506
|
|
|
log.debug("Tech disable iteration") |
|
507
|
|
|
for techID in techs: |
|
508
|
|
|
tech = techs[techID] |
|
509
|
|
|
for tech2ID in tech.researchDisables: |
|
510
|
|
|
tech2 = techs[tech2ID] |
|
511
|
|
|
if techID not in tech2.researchDisables and techID != tech2ID: |
|
512
|
|
|
tech2.researchDisables.append(techID) |
|
513
|
|
|
changed = 1 |
|
514
|
|
|
log.debug("Adding", tech2ID, "DISABLES", techID, ", NOW", tech2.researchDisables) |
|
515
|
|
|
for tech3ID in tech2.researchDisables: |
|
516
|
|
|
tech3 = techs[tech3ID] |
|
517
|
|
|
if tech3ID not in tech.researchDisables and tech3ID != techID: |
|
518
|
|
|
tech.researchDisables.append(tech3ID) |
|
519
|
|
|
changed = 1 |
|
520
|
|
|
log.debug("Adding", techID, "DISABLES", tech3ID, "NOW", tech.researchDisables) |
|
521
|
|
|
|
|
522
|
|
|
# save new specification |
|
523
|
|
|
log.message('Saving specification...') |
|
524
|
|
|
pickle.dump(techs, open(os.path.join(configDir, 'techs.spf'), 'wb'), 1) |
|
525
|
|
|
pickle.dump(Tech, open(os.path.join(configDir, 'Tech.spf'), 'wb'), 1) |
|
526
|
|
|
fh = open(os.path.join(configDir, 'checksum'), 'wb') |
|
527
|
|
|
fh.write(chsum.hexdigest()) |
|
528
|
|
|
fh.close() |
|
529
|
|
|
|
|
530
|
|
|
log.message("There is %d technologies" % len(techs)) |
|
531
|
|
|
|