|
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 copy |
|
22
|
|
|
import math |
|
23
|
|
|
from ige import log |
|
24
|
|
|
from ige.IDataHolder import IDataHolder |
|
25
|
|
|
from ige.ospace import Const |
|
26
|
|
|
from ige.ospace import Rules |
|
27
|
|
|
from ige.ospace import Utils |
|
28
|
|
|
|
|
29
|
|
|
data = IDataHolder() |
|
30
|
|
|
|
|
31
|
|
|
|
|
32
|
|
|
def tool_parseDB(client, db): |
|
33
|
|
|
""" Parses all data in db for needs of other tools. Other in the name |
|
34
|
|
|
means other players. |
|
35
|
|
|
|
|
36
|
|
|
""" |
|
37
|
|
|
global data |
|
38
|
|
|
data.myPlanets = set() |
|
39
|
|
|
data.myProdPlanets = set() |
|
40
|
|
|
data.mySystems = set() |
|
41
|
|
|
data.freePlanets = set() |
|
42
|
|
|
data.freeSystems = set() |
|
43
|
|
|
data.nonhabPlanets = set() |
|
44
|
|
|
data.unknownSystems = set() |
|
45
|
|
|
data.otherPlanets = set() |
|
46
|
|
|
data.otherSystems = set() |
|
47
|
|
|
data.systems = set() |
|
48
|
|
|
data.myFleets = set() |
|
49
|
|
|
data.myMPPerSystem = {} |
|
50
|
|
|
data.myTargetedSystems = set() |
|
51
|
|
|
data.endangeredSystems = {} |
|
52
|
|
|
data.otherFleets = set() |
|
53
|
|
|
data.otherInboundFleets = set() |
|
54
|
|
|
data.idleFleets = set() |
|
55
|
|
|
data.myFleetsWithDesign = {} |
|
56
|
|
|
data.myFleetSheets = {} |
|
57
|
|
|
data.pirateSystems = set() |
|
58
|
|
|
data.relevantSystems = set() |
|
59
|
|
|
data.myRelevantSystems = set() |
|
60
|
|
|
data.distanceToRelevance = {} |
|
61
|
|
|
playerID = client.getPlayerID() |
|
62
|
|
|
player = client.getPlayer() |
|
63
|
|
|
owners = {} |
|
64
|
|
|
for objID in db.keys(): |
|
65
|
|
|
try: |
|
66
|
|
|
obj = db[objID] |
|
67
|
|
|
except KeyError: |
|
68
|
|
|
# TODO find out why there are these errors |
|
69
|
|
|
continue |
|
70
|
|
|
objType = getattr(obj, 'type', None) |
|
71
|
|
|
if objType == Const.T_PLANET: |
|
72
|
|
|
ownerID = getattr(obj, 'owner', None) |
|
73
|
|
|
plSlots = getattr(obj, 'plSlots', 0) |
|
74
|
|
|
slots = getattr(obj, 'slots', []) |
|
75
|
|
|
prodProd = getattr(obj, 'prodProd', 0) |
|
76
|
|
|
plType = getattr(obj, 'plType', None) |
|
77
|
|
|
if plType == u'G' or plType == u'A': |
|
78
|
|
|
data.nonhabPlanets.add(objID) |
|
79
|
|
|
continue |
|
80
|
|
|
if ownerID == playerID and prodProd: |
|
81
|
|
|
data.myProdPlanets.add(objID) |
|
82
|
|
|
data.mySystems.add(obj.compOf) |
|
83
|
|
|
elif ownerID == playerID and not prodProd: |
|
84
|
|
|
# myPlanets are later joined by myProdPlanets |
|
85
|
|
|
data.myPlanets.add(objID) |
|
86
|
|
|
data.mySystems.add(obj.compOf) |
|
87
|
|
|
elif ownerID == Const.OID_NONE and plSlots: |
|
88
|
|
|
data.freePlanets.add(objID) |
|
89
|
|
|
elif not plSlots: |
|
90
|
|
|
data.unknownSystems.add(obj.compOf) |
|
91
|
|
|
else: |
|
92
|
|
|
# systems with owner other than myself, ignore EDEN planets |
|
93
|
|
|
if not ownerID: |
|
94
|
|
|
continue |
|
95
|
|
|
elif ownerID not in owners: |
|
96
|
|
|
owners[ownerID] = client.get(ownerID, publicOnly = 1) |
|
97
|
|
|
|
|
98
|
|
|
if not getattr(owners[ownerID], 'type', Const.OID_NONE) == Const.T_AIEDENPLAYER: |
|
99
|
|
|
data.otherSystems.add(db[objID].compOf) |
|
100
|
|
|
data.otherPlanets.add(objID) |
|
101
|
|
|
if getattr(owners[ownerID], 'type', Const.OID_NONE) in (Const.T_AIPIRPLAYER, Const.T_PIRPLAYER): |
|
102
|
|
|
data.pirateSystems.add(db[objID].compOf) |
|
103
|
|
|
elif objType == Const.T_SYSTEM: |
|
104
|
|
|
if getattr(obj, "starClass", "a")[0] == 'b': |
|
105
|
|
|
# black hole -> nothing to see here, let's ignore it completely |
|
106
|
|
|
continue |
|
107
|
|
|
data.systems.add(objID) |
|
108
|
|
|
if not hasattr(db[objID], 'planets'): |
|
109
|
|
|
data.unknownSystems.add(objID) |
|
110
|
|
|
elif objType == Const.T_FLEET: |
|
111
|
|
|
ownerID = getattr(obj, 'owner', None) |
|
112
|
|
|
if ownerID == playerID: |
|
113
|
|
|
data.myFleets.add(objID) |
|
114
|
|
|
data.myFleetSheets[objID] = getFleetSheet(obj) |
|
115
|
|
|
if len(obj.actions[obj.actionIndex:]) == 0: |
|
116
|
|
|
data.idleFleets.add(objID) |
|
117
|
|
|
for designID in data.myFleetSheets[objID].keys(): |
|
118
|
|
|
if not data.myFleetsWithDesign.get(designID, set()): |
|
119
|
|
|
data.myFleetsWithDesign[designID] = set([objID]) |
|
120
|
|
|
else: |
|
121
|
|
|
data.myFleetsWithDesign[designID] |= set([objID]) |
|
122
|
|
|
else: |
|
123
|
|
|
data.otherFleets.add(objID) |
|
124
|
|
|
# ================== |
|
125
|
|
|
# second phase |
|
126
|
|
|
# analyzing fleet action queues |
|
127
|
|
|
for fleetID in data.myFleets: |
|
128
|
|
|
fleet = db[fleetID] |
|
129
|
|
|
for orType, orTargID, orData in fleet.actions[fleet.actionIndex:]: |
|
130
|
|
|
if orType == Const.FLACTION_WAIT: |
|
131
|
|
|
continue |
|
132
|
|
|
elif orType == Const.FLACTION_REPEATFROM: |
|
133
|
|
|
continue |
|
134
|
|
|
elif orType == Const.FLACTION_REDIRECT: |
|
135
|
|
|
if orTargID == Const.OID_NONE: |
|
136
|
|
|
continue |
|
137
|
|
|
orTarg = db[orTargID] |
|
138
|
|
|
if orTarg.type == Const.T_SYSTEM: |
|
139
|
|
|
data.unknownSystems -= set([orTargID]) |
|
140
|
|
|
elif orTarg.type == Const.T_PLANET: |
|
141
|
|
|
data.unknownSystems -= set([orTarg.compOf]) |
|
142
|
|
|
# deploy order removes target from free planets set |
|
143
|
|
|
# if deploying to non-free planet, change order TODO [non-systematic] |
|
144
|
|
|
if orType == Const.FLACTION_DEPLOY: |
|
145
|
|
|
if orTargID in data.freePlanets: |
|
146
|
|
|
data.freePlanets -= set([orTargID]) |
|
147
|
|
|
else: |
|
148
|
|
|
client.cmdProxy.deleteAction(fleetID, fleet.actionIndex) |
|
149
|
|
|
# fill data.myMPPerSystem |
|
150
|
|
|
if len(fleet.actions[fleet.actionIndex:]) == 0: |
|
151
|
|
|
if fleet.orbiting in data.mySystems: |
|
152
|
|
|
try: |
|
153
|
|
|
data.myMPPerSystem[fleet.orbiting] += fleet.combatPwr |
|
154
|
|
|
except KeyError: |
|
155
|
|
|
data.myMPPerSystem[fleet.orbiting] = fleet.combatPwr |
|
156
|
|
|
else: |
|
157
|
|
|
lastOrder = fleet.actions[len(fleet.actions)-1] |
|
158
|
|
|
targetID = lastOrder[1] |
|
159
|
|
|
if targetID in data.myPlanets: |
|
160
|
|
|
sysID = db[targetID].compOf |
|
161
|
|
|
try: |
|
162
|
|
|
data.myMPPerSystem[sysID] += fleet.combatPwr |
|
163
|
|
|
except KeyError: |
|
164
|
|
|
data.myMPPerSystem[sysID] = fleet.combatPwr |
|
165
|
|
|
elif targetID in data.mySystems: |
|
166
|
|
|
try: |
|
167
|
|
|
data.myMPPerSystem[targetID] += fleet.combatPwr |
|
168
|
|
|
except KeyError: |
|
169
|
|
|
data.myMPPerSystem[targetID] = fleet.combatPwr |
|
170
|
|
|
|
|
171
|
|
|
data.myPlanets |= data.myProdPlanets |
|
172
|
|
|
# only systems with free or nonhabitable planets are considered free |
|
173
|
|
|
for systemID in data.systems: |
|
174
|
|
|
isEmpty = True |
|
175
|
|
|
hasEmpty = False |
|
176
|
|
|
planets = set(getattr(db[systemID], 'planets', [])) |
|
177
|
|
|
if planets and not planets - data.freePlanets - data.nonhabPlanets: |
|
178
|
|
|
data.freeSystems.add(systemID) |
|
179
|
|
|
# find attacking fleets |
|
180
|
|
|
for fleetID in data.otherFleets: |
|
181
|
|
|
fleet = db[fleetID] |
|
182
|
|
|
if getattr(fleet, 'target', None): |
|
183
|
|
|
targetID = getattr(fleet, 'target', None) |
|
184
|
|
|
elif not getattr(fleet, 'orbiting', Const.OID_NONE) == Const.OID_NONE: |
|
185
|
|
|
targetID = getattr(fleet, 'orbiting', Const.OID_NONE) |
|
186
|
|
|
if targetID: |
|
|
|
|
|
|
187
|
|
|
if targetID in data.myPlanets: |
|
188
|
|
|
data.myTargetedSystems.add(db[targetID].compOf) |
|
189
|
|
|
data.otherInboundFleets.add(fleetID) |
|
190
|
|
|
elif targetID in data.mySystems: |
|
191
|
|
|
data.myTargetedSystems.add(targetID) |
|
192
|
|
|
data.otherInboundFleets.add(fleetID) |
|
193
|
|
|
return |
|
194
|
|
|
|
|
195
|
|
|
def doRelevance(client, db, rangeOfRelevance): |
|
196
|
|
|
""" This function finds all systems, which are nearer to the players |
|
197
|
|
|
system than is defined in rangeOfRelevance. This is saved in |
|
198
|
|
|
data.relevantSystems. |
|
199
|
|
|
Then, it saves all players planets in relevant distance from any |
|
200
|
|
|
planet in data.otherSystems. And finally, it fills dictionary |
|
201
|
|
|
data.distanceToRelevance, where each system of the player got its |
|
202
|
|
|
distance to nearest relevant system of the player. |
|
203
|
|
|
|
|
204
|
|
|
""" |
|
205
|
|
|
global data |
|
206
|
|
|
for systemID in data.systems: |
|
207
|
|
|
system = db[systemID] |
|
208
|
|
|
for tempID in data.mySystems: |
|
209
|
|
|
temp = db[tempID] |
|
210
|
|
|
distance = math.hypot(system.x - temp.x, system.y - temp.y) |
|
211
|
|
|
if distance <= rangeOfRelevance: |
|
212
|
|
|
data.relevantSystems.add(systemID) |
|
213
|
|
|
break |
|
214
|
|
|
for systemID in data.mySystems: |
|
215
|
|
|
system = db[systemID] |
|
216
|
|
|
for tempID in data.otherSystems: |
|
217
|
|
|
temp = db[tempID] |
|
218
|
|
|
distance = math.hypot(system.x - temp.x, system.y - temp.y) |
|
219
|
|
|
if distance <= rangeOfRelevance: |
|
220
|
|
|
data.myRelevantSystems.add(systemID) |
|
221
|
|
|
break |
|
222
|
|
|
for systemID in data.mySystems - data.myRelevantSystems: |
|
223
|
|
|
system = db[systemID] |
|
224
|
|
|
relDist = 99999 |
|
225
|
|
|
for tempID in data.myRelevantSystems: |
|
226
|
|
|
temp = db[tempID] |
|
227
|
|
|
distance = math.hypot(system.x - temp.x, system.y - temp.y) |
|
228
|
|
|
relDist = min(relDist, distance) |
|
229
|
|
|
data.distanceToRelevance[systemID] = relDist |
|
230
|
|
|
|
|
231
|
|
|
def findInfluence(client, db, rangeOfInfluence, objectIDList): |
|
232
|
|
|
""" Returns list of all systems, which distance to any object |
|
233
|
|
|
from the objectList is less than rangeOfInfluence. |
|
234
|
|
|
|
|
235
|
|
|
objectList -- iterable of IDs, and each of the objects in the db has |
|
236
|
|
|
to have .x and .y numeric parameters. |
|
237
|
|
|
|
|
238
|
|
|
""" |
|
239
|
|
|
global data |
|
240
|
|
|
influencedSystems = set() |
|
241
|
|
|
for systemID in data.systems: |
|
242
|
|
|
system = db[systemID] |
|
243
|
|
|
for tempID in objectIDList: |
|
244
|
|
|
temp = db[tempID] |
|
245
|
|
|
distance = math.hypot(system.x - temp.x, system.y - temp.y) |
|
246
|
|
|
if distance <= rangeOfInfluence: |
|
247
|
|
|
influencedSystems.add(systemID) |
|
248
|
|
|
return influencedSystems |
|
249
|
|
|
|
|
250
|
|
|
def doDanger(client, db): |
|
251
|
|
|
""" Fills data.endangeredSystems dictionary. Each system of the player, |
|
252
|
|
|
to which is heading some fleet of other player with military power |
|
253
|
|
|
got its own record consisting of military power and number of ships heading |
|
254
|
|
|
there. (It is the sum of all fleets). |
|
255
|
|
|
Medium and large ships are counted as 2 and 4 ships each respectively. |
|
256
|
|
|
|
|
257
|
|
|
""" |
|
258
|
|
|
global data |
|
259
|
|
|
for fleetID in data.otherInboundFleets: |
|
260
|
|
|
fleet = db[fleetID] |
|
261
|
|
|
if not getattr(fleet, 'combatPwr', 0): |
|
262
|
|
|
continue |
|
263
|
|
|
if not getattr(fleet, 'orbiting', Const.OID_NONE) == Const.OID_NONE: |
|
264
|
|
|
targID = fleet.orbiting |
|
265
|
|
|
elif hasattr(fleet, 'target'): |
|
266
|
|
|
targID = fleet.target |
|
267
|
|
|
else: |
|
268
|
|
|
continue |
|
269
|
|
|
if targID in data.endangeredSystems: |
|
270
|
|
|
milPow, ships = data.endangeredSystems[targID] |
|
271
|
|
|
else: |
|
272
|
|
|
milPow = ships = 0 |
|
273
|
|
|
milPow += fleet.combatPwr |
|
274
|
|
|
if hasattr(fleet, 'shipScan'): |
|
275
|
|
|
for (name, shipClass, isMilitary), quantity in fleet.shipScan.items(): |
|
276
|
|
|
if isMilitary: |
|
277
|
|
|
ships += quantity * (shipClass + 1) ** 2 |
|
278
|
|
|
data.endangeredSystems[targID] = (milPow, ships) |
|
279
|
|
|
elif milPow > 0: |
|
280
|
|
|
data.endangeredSystems[targID] = (milPow, ships) |
|
281
|
|
|
|
|
282
|
|
|
def orderFleet(client, db, fleetID, order, targetID, orderData): |
|
283
|
|
|
""" Orders fleet to do something. It removes old actions |
|
284
|
|
|
to prevent queue overflow. |
|
285
|
|
|
""" |
|
286
|
|
|
fleet = db[fleetID] |
|
287
|
|
|
fleet.actions, fleet.actionIndex = client.cmdProxy.clearProcessedActions(fleetID) |
|
288
|
|
|
client.cmdProxy.addAction(fleetID, fleet.actionIndex+1, order, targetID, orderData) |
|
289
|
|
|
return |
|
290
|
|
|
|
|
291
|
|
|
def getFleetSheet(fleet): |
|
292
|
|
|
""" Returns dictionary with key being design number, and value |
|
293
|
|
|
number of ships in the fleet of that design. |
|
294
|
|
|
""" |
|
295
|
|
|
|
|
296
|
|
|
sheet = {} |
|
297
|
|
|
for ship in fleet.ships: |
|
298
|
|
|
try: |
|
299
|
|
|
sheet[ship[0]] += 1 |
|
300
|
|
|
except KeyError: |
|
301
|
|
|
sheet[ship[0]] = 1 |
|
302
|
|
|
return sheet |
|
303
|
|
|
|
|
304
|
|
|
def getSubfleet(fleet, ships, needsExact): |
|
305
|
|
|
""" Returns subfleet roster. |
|
306
|
|
|
|
|
307
|
|
|
fleet - fleet object from which the subfleet is taken |
|
308
|
|
|
ships - is dictionary with keys being design IDs, values are |
|
309
|
|
|
demanded quantities, with value = 0 meaning "return all of the type" |
|
310
|
|
|
None value means "return whole fleet" |
|
311
|
|
|
needsExact - if true, all items in ships has to be in place to return |
|
312
|
|
|
the roster, except 0 value, which means "all" |
|
313
|
|
|
""" |
|
314
|
|
|
|
|
315
|
|
|
newShips = {} |
|
316
|
|
|
wholeFleet = getFleetSheet(fleet) |
|
317
|
|
|
if not ships: |
|
318
|
|
|
return wholeFleet |
|
319
|
|
|
for desID in ships: |
|
320
|
|
|
if ships[desID] == 0: |
|
321
|
|
|
try: |
|
322
|
|
|
newShips[desID] = wholeFleet[desID] |
|
323
|
|
|
except KeyError: |
|
324
|
|
|
continue |
|
325
|
|
|
else: |
|
326
|
|
|
try: |
|
327
|
|
|
if needsExact and wholeFleet[desID] < ships[desID]: |
|
328
|
|
|
return None |
|
329
|
|
|
newShips[desID] = min(ships[desID], wholeFleet[desID]) |
|
330
|
|
|
except KeyError: |
|
331
|
|
|
if needsExact: |
|
332
|
|
|
return None |
|
333
|
|
|
return newShips |
|
334
|
|
|
|
|
335
|
|
|
def fleetContains(fleet, ships): |
|
336
|
|
|
""" Tests whether fleet contains all ships in "ships" dictionary. |
|
337
|
|
|
|
|
338
|
|
|
Returns boolean value. |
|
339
|
|
|
""" |
|
340
|
|
|
|
|
341
|
|
|
sheet = getFleetSheet(fleet) |
|
342
|
|
|
for desID in ships: |
|
343
|
|
|
try: |
|
344
|
|
|
if ships[desID] > sheet[desID]: |
|
345
|
|
|
return False |
|
346
|
|
|
except KeyError: |
|
347
|
|
|
return False |
|
348
|
|
|
return True |
|
349
|
|
|
|
|
350
|
|
|
def orderPartFleet(client, db, ships, needsExact, fleetID, order, targetID, orderData): |
|
351
|
|
|
""" Splits part of the fleet and assign it the order. |
|
352
|
|
|
|
|
353
|
|
|
ships - is dictionary with keys being design IDs, value is |
|
354
|
|
|
demanded integer, with value = 0 it means "send all" |
|
355
|
|
|
needsExact - if true, send all ships in the "ships" or don't |
|
356
|
|
|
send anything, if false, send at least what you have, |
|
357
|
|
|
if you don't have all of them |
|
358
|
|
|
""" |
|
359
|
|
|
|
|
360
|
|
|
sheet = getFleetSheet(db[fleetID]) |
|
361
|
|
|
for key in ships: |
|
362
|
|
|
try: |
|
363
|
|
|
if ships[key] == 0: |
|
364
|
|
|
ships[key] = sheet[key] |
|
365
|
|
|
except KeyError: |
|
366
|
|
|
continue |
|
367
|
|
|
if sheet == ships: |
|
368
|
|
|
orderFleet(client, db, fleetID, order, targetID, orderData) |
|
369
|
|
|
return None, db[fleetID], client.getPlayer().fleets |
|
370
|
|
|
isValid = True |
|
371
|
|
|
sendShips = {} |
|
372
|
|
|
for key in ships: |
|
373
|
|
|
try: |
|
374
|
|
|
if ships[key] > sheet[key] and needsExact: |
|
375
|
|
|
return None, db[fleetID], client.getPlayer().fleets |
|
376
|
|
|
elif ships[key] == 0: |
|
377
|
|
|
sendShips[key] = sheet[key] |
|
378
|
|
|
else: |
|
379
|
|
|
sendShips[key] = min(ships[key], sheet[key]) |
|
380
|
|
|
except KeyError: |
|
381
|
|
|
if needsExact: |
|
382
|
|
|
return None, db[fleetID], client.getPlayer().fleets |
|
383
|
|
|
if sendShips: |
|
384
|
|
|
newShips = [] |
|
385
|
|
|
newMaxEn = 0 |
|
386
|
|
|
for ship in db[fleetID].ships: |
|
387
|
|
|
if sendShips.get(ship[0], 0) > 0: |
|
388
|
|
|
sendShips[ship[0]] -= 1 |
|
389
|
|
|
newShips.append(ship) |
|
390
|
|
|
newMaxEn += client.getPlayer().shipDesigns[ship[0]].storEn |
|
391
|
|
|
if newShips: |
|
392
|
|
|
newFleet, origFleet, fleetsout = client.cmdProxy.splitFleet(fleetID, |
|
393
|
|
|
newShips, |
|
394
|
|
|
newMaxEn) |
|
395
|
|
|
db[newFleet.oid] = newFleet |
|
396
|
|
|
db[origFleet.oid] = origFleet |
|
397
|
|
|
orderFleet(client, db, newFleet.oid, order, targetID, orderData) |
|
398
|
|
|
return newFleet, origFleet, fleetsout |
|
399
|
|
|
return None, db[fleetID], client.getPlayer().fleets |
|
400
|
|
|
else: |
|
401
|
|
|
None, db[fleetID], client.getPlayer().fleets |
|
402
|
|
|
|
|
403
|
|
|
def subfleetMaxRange(client, db, ships, fleetID, canDamage=False): |
|
404
|
|
|
""" Counts range of subfleet in parsecs |
|
405
|
|
|
|
|
406
|
|
|
ships - is dictionary with keys being design IDs, value is |
|
407
|
|
|
demanded number, with value = 0 it means "send all of the type" |
|
408
|
|
|
None value means "send all" ships |
|
409
|
|
|
""" |
|
410
|
|
|
|
|
411
|
|
|
fleet = db[fleetID] |
|
412
|
|
|
subfleet = getSubfleet(fleet, ships, True) |
|
413
|
|
|
if not subfleet: |
|
414
|
|
|
return 0.0 |
|
415
|
|
|
player = client.getPlayer() |
|
416
|
|
|
storEn = 0 |
|
417
|
|
|
operEn = 0 |
|
418
|
|
|
speed = 99999 |
|
419
|
|
|
for desID in subfleet: |
|
420
|
|
|
design = player.shipDesigns[desID] |
|
421
|
|
|
storEn += design.storEn * subfleet[desID] |
|
422
|
|
|
operEn += design.operEn * subfleet[desID] |
|
423
|
|
|
speed = min(design.speed, speed) |
|
424
|
|
|
storEn = min(storEn, fleet.storEn) |
|
425
|
|
|
return storEn / operEn * speed / 24 |
|
426
|
|
|
|
|
427
|
|
|
def findNearest(db, obj, targets, maxDist=99999, number=1): |
|
428
|
|
|
""" Searches through the targets, and returns list consisting of number of |
|
429
|
|
|
the nearest objects to the objID, sorted from the nearest to the farthest. |
|
430
|
|
|
Only requirement is that every item needs to have attributes x and y. |
|
431
|
|
|
|
|
432
|
|
|
obj - the _object_ [not ID!] we try to find neighbours for |
|
433
|
|
|
targets - set of object IDs |
|
434
|
|
|
maxDist - maximum allowed distance |
|
435
|
|
|
number - size of the required set, when number of targets is lesser |
|
436
|
|
|
than number, it will just sort the targetIDs accordingly |
|
437
|
|
|
""" |
|
438
|
|
|
distances = {} |
|
439
|
|
|
x, y = obj.x, obj.y |
|
440
|
|
|
for targID in targets: |
|
441
|
|
|
target = db[targID] |
|
442
|
|
|
distance = math.hypot(x - target.x, y - target.y) |
|
443
|
|
|
if distance not in distances: |
|
444
|
|
|
distances[distance] = set([targID]) |
|
445
|
|
|
else: |
|
446
|
|
|
distances[distance] |= set([targID]) |
|
447
|
|
|
relevantKeys = sorted(distances.keys())[:number] |
|
448
|
|
|
final = [] |
|
449
|
|
|
for key in relevantKeys: |
|
450
|
|
|
if key > maxDist: |
|
451
|
|
|
break |
|
452
|
|
|
for targID in distances[key]: |
|
453
|
|
|
final.append(targID) |
|
454
|
|
|
number -= 1 |
|
455
|
|
|
if not number: break |
|
456
|
|
|
if not number: break |
|
457
|
|
|
return final |
|
458
|
|
|
|
|
459
|
|
|
def findPopCenterPlanets(db, planetsIDs): |
|
460
|
|
|
""" It finds "center of mass" of population. |
|
461
|
|
|
|
|
462
|
|
|
Returns sorted list of all planets, beginning with those nearest to the |
|
463
|
|
|
found center. |
|
464
|
|
|
|
|
465
|
|
|
""" |
|
466
|
|
|
x = 0 |
|
467
|
|
|
y = 0 |
|
468
|
|
|
population = 0 |
|
469
|
|
|
for planetID in planetsIDs: |
|
470
|
|
|
planet = db[planetID] |
|
471
|
|
|
x += planet.x * planet.storPop |
|
472
|
|
|
y += planet.y * planet.storPop |
|
473
|
|
|
population += planet.storPop |
|
474
|
|
|
x /= population |
|
475
|
|
|
y /= population |
|
476
|
|
|
fakeObj = IDataHolder() |
|
477
|
|
|
fakeObj.x = x |
|
478
|
|
|
fakeObj.y = y |
|
479
|
|
|
return findNearest(db, fakeObj, planetsIDs, maxDist=99999, number=len(planetsIDs)) |
|
480
|
|
|
|
|
481
|
|
|
def orderFromSystem(client, db, ships, systemID, order, targetID, orderData): |
|
482
|
|
|
""" Tries to send ships defined by ships dictionary, and using all |
|
483
|
|
|
idle fleets in the system. |
|
484
|
|
|
ships - is dictionary with keys being design IDs, value is |
|
485
|
|
|
demanded number, with value = 0 it means "send all of the type" |
|
486
|
|
|
None value means "send all" ships |
|
487
|
|
|
systemID - ID of the system from which are fleets send |
|
488
|
|
|
order, targetID, orderData - parameters of the order |
|
489
|
|
|
|
|
490
|
|
|
Returns dictionary of ships _which remains to be send_, ie what of the |
|
491
|
|
|
ships dictionary wasn't available in the system, and military power of |
|
492
|
|
|
the send ships. |
|
493
|
|
|
|
|
494
|
|
|
""" |
|
495
|
|
|
log.debug('ai_tools orderFromSystem', ships, systemID) |
|
496
|
|
|
system = db[systemID] |
|
497
|
|
|
fleetsIDs = set(system.fleets) & data.idleFleets |
|
498
|
|
|
milPow = 0 |
|
499
|
|
|
if len(fleetsIDs) == 0: |
|
500
|
|
|
return ships, 0 |
|
501
|
|
|
for fleetID in fleetsIDs: |
|
502
|
|
|
if ships == None: |
|
503
|
|
|
orderFleet(client, db, fleetID, order, targetID, orderData) |
|
504
|
|
|
continue |
|
505
|
|
|
fleet = db[fleetID] |
|
506
|
|
|
sheet = getFleetSheet(fleet) |
|
507
|
|
|
toSend = {} |
|
508
|
|
|
for key in copy.copy(ships): |
|
509
|
|
|
try: |
|
510
|
|
|
toSend[key] = min(ships[key], sheet[key]) |
|
511
|
|
|
ships[key] = max(ships[key] - sheet[key], 0) |
|
512
|
|
|
if ships[key] == 0: |
|
513
|
|
|
del ships[key] |
|
514
|
|
|
except KeyError: |
|
515
|
|
|
continue |
|
516
|
|
|
if toSend == {}: |
|
517
|
|
|
continue |
|
518
|
|
|
log.debug('ai_tools orderFromSystem - sending', toSend, fleetID) |
|
519
|
|
|
newFleet, origFleet, fleetsout = orderPartFleet(client, db, toSend, False, fleetID, order, targetID, orderData) |
|
520
|
|
|
milPow += getattr(newFleet, 'combatPwr', 0) |
|
521
|
|
|
hasAll = True |
|
522
|
|
|
for key in ships: |
|
523
|
|
|
if not ships[key] == 0: |
|
524
|
|
|
hasAll = False |
|
525
|
|
|
break |
|
526
|
|
|
if hasAll: break |
|
527
|
|
|
return ships, milPow |
|
528
|
|
|
|
|
529
|
|
|
def sortStructures(client, db, planetID): |
|
530
|
|
|
""" Moves structures on the planet, so on the left are buildings producing |
|
531
|
|
|
food, then buildings producing electricity, then buildings producing CPs. |
|
532
|
|
|
Those with more than one relevant parameters "such as food + CPs" are |
|
533
|
|
|
in the more important group [food > en > CP > rest]. |
|
534
|
|
|
|
|
535
|
|
|
""" |
|
536
|
|
|
planet = db[planetID] |
|
537
|
|
|
player = client.getPlayer() |
|
538
|
|
|
structs = {} |
|
539
|
|
|
bioStructs = [] |
|
540
|
|
|
enStructs = [] |
|
541
|
|
|
prodStructs = [] |
|
542
|
|
|
# fill the groups with positions of relevant structures |
|
543
|
|
|
for techID, hp, something, eff in planet.slots: |
|
544
|
|
|
techBio, techEn, techProd = getSystemStatsChange(client, db, techID, planetID, 0) |
|
545
|
|
|
if techBio > 0: |
|
546
|
|
|
bioStructs.append(techID) |
|
547
|
|
|
elif techEn > 0: |
|
548
|
|
|
enStructs.append(techID) |
|
549
|
|
|
elif techProd > 0: |
|
550
|
|
|
prodStructs.append(techID) |
|
551
|
|
|
# how many moves are necessary? As we move each group separately, we |
|
552
|
|
|
# assume, that structure in "bio" group has to be in |
|
553
|
|
|
# position < len(bioStructs), same with other groups |
|
554
|
|
|
needMoves = len(planet.slots) |
|
555
|
|
|
pos = 0 |
|
556
|
|
|
for techID, hp, something, eff in planet.slots: |
|
557
|
|
|
if pos < len(bioStructs) and techID not in bioStructs: |
|
558
|
|
|
needMoves = pos |
|
559
|
|
|
break |
|
560
|
|
|
elif pos >= len(bioStructs) and pos < len(bioStructs) + len(enStructs) and techID not in enStructs: |
|
561
|
|
|
needMoves = pos |
|
562
|
|
|
break |
|
563
|
|
|
elif pos >= len(bioStructs) + len(enStructs) and pos < len(bioStructs) + len(enStructs) + len(prodStructs) and techID not in prodStructs: |
|
564
|
|
|
needMoves = pos |
|
565
|
|
|
break |
|
566
|
|
|
else: |
|
567
|
|
|
pos += 1 |
|
568
|
|
|
# pos will be used once again |
|
569
|
|
|
# we are correcting the order from left to right, so next struct won't |
|
570
|
|
|
# get its position changed until it is moved itself |
|
571
|
|
|
# move is made to the leftmost position of the group |
|
572
|
|
|
for techID, hp, something, eff in planet.slots[needMoves:]: |
|
573
|
|
|
if techID in bioStructs: |
|
574
|
|
|
client.cmdProxy.moveStruct(planetID, pos, 0 - pos) |
|
575
|
|
|
pos += 1 |
|
576
|
|
|
pos = max(needMoves, len(bioStructs)) |
|
577
|
|
|
needMoves = pos |
|
578
|
|
|
for techID, hp, something, eff in planet.slots[needMoves:]: |
|
579
|
|
|
if techID in enStructs: |
|
580
|
|
|
client.cmdProxy.moveStruct(planetID, pos, len(bioStructs) - pos) |
|
581
|
|
|
pos += 1 |
|
582
|
|
|
pos = max(needMoves, len(bioStructs) + len(enStructs)) |
|
583
|
|
|
needMoves = pos |
|
584
|
|
|
for techID, hp, something, eff in planet.slots[needMoves:]: |
|
585
|
|
|
if techID in prodStructs: |
|
586
|
|
|
client.cmdProxy.moveStruct(planetID, pos, len(bioStructs) + len(enStructs) - pos) |
|
587
|
|
|
pos += 1 |
|
588
|
|
|
return |
|
589
|
|
|
|
|
590
|
|
|
def getSystemStructStats(client, db, systemID, processQueues=True): |
|
591
|
|
|
""" It go through all planets and structures, and creates IDataHolder |
|
592
|
|
|
object, with roster of buildings, surplus of bio and en. |
|
593
|
|
|
|
|
594
|
|
|
processQueues - if True, it go through all buildQueues and adjust all |
|
595
|
|
|
statistics as it would be all done already. |
|
596
|
|
|
|
|
597
|
|
|
Returns IDataHolder with parameters: |
|
598
|
|
|
.bio - system surplus of biomass |
|
599
|
|
|
.en - system surplus of en |
|
600
|
|
|
.planets - dictionary, keys are planetIDs of players or free planets, |
|
601
|
|
|
and values are dictionaries (huh) with keys being techIDs |
|
602
|
|
|
and values being number of those structs. |
|
603
|
|
|
|
|
604
|
|
|
""" |
|
605
|
|
|
systemStats = IDataHolder() |
|
606
|
|
|
system = db[systemID] |
|
607
|
|
|
player = client.getPlayer() |
|
608
|
|
|
myPlanets = set(system.planets) & data.myPlanets |
|
609
|
|
|
systemStats.planets = {} |
|
610
|
|
|
for planetID in myPlanets: |
|
611
|
|
|
systemStats.planets[planetID] = {} |
|
612
|
|
|
for planetID in set(system.planets) & data.freePlanets: |
|
613
|
|
|
systemStats.planets[planetID] = {} |
|
614
|
|
|
# creation of the .planets dictionary |
|
615
|
|
|
for planetID in myPlanets: |
|
616
|
|
|
planet = db[planetID] |
|
617
|
|
|
for techID, hp, something, eff in planet.slots: |
|
618
|
|
|
try: |
|
619
|
|
|
systemStats.planets[planetID][techID] += 1 |
|
620
|
|
|
except KeyError: |
|
621
|
|
|
systemStats.planets[planetID][techID] = 1 |
|
622
|
|
|
if not processQueues: |
|
623
|
|
|
# do not look into the queue |
|
624
|
|
|
continue |
|
625
|
|
|
for task in getattr(planet, 'prodQueue', []): |
|
626
|
|
|
if not task.isShip: |
|
627
|
|
|
techID = task.techID |
|
628
|
|
|
tech = client.getFullTechInfo(task.techID) |
|
629
|
|
|
if tech.isStructure: |
|
630
|
|
|
if task.targetID not in systemStats.planets.keys(): |
|
631
|
|
|
continue |
|
632
|
|
|
try: |
|
633
|
|
|
systemStats.planets[task.targetID][techID] += 1 |
|
634
|
|
|
except KeyError: |
|
635
|
|
|
systemStats.planets[task.targetID][techID] = 1 |
|
636
|
|
|
if task.demolishStruct: |
|
637
|
|
|
try: |
|
638
|
|
|
systemStats.planets[task.targetID][task.demolishStruct] -= 1 |
|
639
|
|
|
except KeyError: |
|
640
|
|
|
systemStats.planets[task.targetID][task.demolishStruct] = -1 |
|
641
|
|
|
# by parsing .planets object, fill the .bio and .en parameters |
|
642
|
|
|
systemStats.bio = 0 |
|
643
|
|
|
systemStats.en = 0 |
|
644
|
|
|
for planetID in systemStats.planets: |
|
645
|
|
|
planet = db[planetID] |
|
646
|
|
|
if planetID not in myPlanets: |
|
647
|
|
|
continue |
|
648
|
|
|
for techID in systemStats.planets[planetID]: |
|
649
|
|
|
quantity = systemStats.planets[planetID][techID] |
|
650
|
|
|
deltaBio, deltaEn, deltaProd = getSystemStatsChange(client, db, techID, planetID, 0) |
|
651
|
|
|
tech = client.getFullTechInfo(techID) |
|
652
|
|
|
systemStats.en += quantity * deltaEn |
|
653
|
|
|
systemStats.bio += quantity * deltaBio |
|
654
|
|
|
return systemStats |
|
655
|
|
|
|
|
656
|
|
|
def getSystemStatsChange(client, db, techID, targetPlanetID, targetTechID): |
|
657
|
|
|
""" Find out, how are the stats going to change with build of structure |
|
658
|
|
|
with techID, on targetPlanetID, over targetTechID. |
|
659
|
|
|
|
|
660
|
|
|
deltaProd - it is RAW production, ie no morale bonuses etc. |
|
661
|
|
|
|
|
662
|
|
|
""" |
|
663
|
|
|
planet = db[targetPlanetID] |
|
664
|
|
|
player = client.getPlayer() |
|
665
|
|
|
deltaBio = 0 |
|
666
|
|
|
deltaEn = 0 |
|
667
|
|
|
tech = client.getFullTechInfo(techID) |
|
668
|
|
|
deltaEn -= tech.operEn |
|
669
|
|
|
deltaBio -= tech.operWorkers / 100 |
|
670
|
|
|
deltaEn += tech.prodEn * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodEnMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
671
|
|
|
deltaBio += tech.prodBio * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodBioMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
672
|
|
|
deltaProd = tech.prodProd * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodProdMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
673
|
|
|
if targetTechID: |
|
674
|
|
|
tech = client.getFullTechInfo(targetTechID) |
|
675
|
|
|
deltaEn += tech.operEn |
|
676
|
|
|
deltaBio += tech.operWorkers / 100 |
|
677
|
|
|
deltaEn -= tech.prodEn * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodEnMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
678
|
|
|
deltaBio -= tech.prodBio * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodBioMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
679
|
|
|
deltaProd -= tech.prodProd * Rules.techImprEff[player.techs.get(techID, 1)] * sum([x*y/100.0 for x, y in zip(tech.prodProdMod, [planet.plBio, planet.plMin, planet.plEn, 100])]) |
|
680
|
|
|
return deltaBio, deltaEn, deltaProd |
|
681
|
|
|
|
|
682
|
|
|
def checkBuildQueues(client, db, systemID, prodPlanets): |
|
683
|
|
|
system = db[systemID] |
|
684
|
|
|
player = client.getPlayer() |
|
685
|
|
|
for planetID in prodPlanets: |
|
686
|
|
|
planet = db[planetID] |
|
687
|
|
|
validTasks = 0 |
|
688
|
|
|
while len(planet.prodQueue) > validTasks: |
|
689
|
|
|
# validTasks is effectively the actual index |
|
690
|
|
|
task = planet.prodQueue[validTasks] |
|
691
|
|
|
if task.targetID in data.myPlanets | data.freePlanets | set([Const.OID_NONE, None]): |
|
692
|
|
|
validTasks += 1 |
|
693
|
|
|
else: |
|
694
|
|
|
planet.prodQueue, player.stratRes = client.cmdProxy.abortConstruction(planetID, validTasks) |
|
695
|
|
|
|
|
696
|
|
|
def buildSystem(client, db, systemID, prodPlanets, finalSystemPlan): |
|
697
|
|
|
""" Assigns tasks to all idle planets with CP > 0 in one system, according |
|
698
|
|
|
to object finalSystemPlan. There is NO guaranty it will rebuild it correctly |
|
699
|
|
|
as no math model was made for it. It just try to build most effectively, |
|
700
|
|
|
with keeping system in each step self sufficient. |
|
701
|
|
|
For functioning correctly, it is probably necessary to have some |
|
702
|
|
|
reserves [one planets builds slowly big farm, another knowing it builds |
|
703
|
|
|
factory over only outpost, .. :)] |
|
704
|
|
|
|
|
705
|
|
|
finalSystemPlan - dictionary, keys are planetIDs of players or free planets, |
|
706
|
|
|
and values are dictionaries with keys being techIDs |
|
707
|
|
|
and values being number of those structs. |
|
708
|
|
|
|
|
709
|
|
|
""" |
|
710
|
|
|
system = db[systemID] |
|
711
|
|
|
player = client.getPlayer() |
|
712
|
|
|
structStats = getSystemStructStats(client, db, systemID) |
|
713
|
|
|
structsToBuild = {} |
|
714
|
|
|
structsToDemolish = {} |
|
715
|
|
|
difference = {} |
|
716
|
|
|
checkBuildQueues(client, db, systemID, prodPlanets) |
|
717
|
|
|
# parse final plan to set buildings which need to be build and those that |
|
718
|
|
|
# may be demolished |
|
719
|
|
|
for planetID in finalSystemPlan: |
|
720
|
|
|
difference[planetID] = Utils.dictSubtraction(finalSystemPlan[planetID], structStats.planets[planetID]) |
|
721
|
|
|
for planetID in difference: |
|
722
|
|
|
structsToBuild[planetID] = {} |
|
723
|
|
|
structsToDemolish[planetID] = {} |
|
724
|
|
|
for techID in difference[planetID]: |
|
725
|
|
|
if difference[planetID][techID] > 0: |
|
726
|
|
|
structsToBuild[planetID][techID] = difference[planetID][techID] |
|
727
|
|
|
elif difference[planetID][techID] < 0: |
|
728
|
|
|
structsToDemolish[planetID][techID] = difference[planetID][techID] |
|
729
|
|
|
idlePlanets = copy.copy(prodPlanets) |
|
730
|
|
|
for planetID in prodPlanets: |
|
731
|
|
|
planet = db[planetID] |
|
732
|
|
|
if getattr(planet, 'prodQueue', None): |
|
733
|
|
|
# something in the build queue, skip the planet |
|
734
|
|
|
idlePlanets.remove(planetID) |
|
735
|
|
|
continue |
|
736
|
|
|
# start the most effective project [CP-wise], which is still leaving |
|
737
|
|
|
# sustainable system |
|
738
|
|
|
toBuild = getStructBuildEffectivity(client, db, planetID, structsToBuild.keys(), structsToBuild, structsToDemolish) |
|
739
|
|
|
for techID, targetPlanetID, targetTechID in toBuild: |
|
740
|
|
|
targetPlanet = db[targetPlanetID] |
|
741
|
|
|
if len(targetPlanet.slots) == targetPlanet.plSlots and targetTechID == Const.OID_NONE: |
|
742
|
|
|
continue |
|
743
|
|
|
deltaBio, deltaEn, deltaProd = getSystemStatsChange(client, db, techID, targetPlanetID, targetTechID) |
|
744
|
|
|
if structStats.bio + deltaBio >= 0 and structStats.en + deltaEn >= 0: |
|
745
|
|
|
planet.prodQueue, player.stratRes = client.cmdProxy.startConstruction(planetID, |
|
746
|
|
|
techID, 1, targetPlanetID, techID < 1000, 0, targetTechID) |
|
747
|
|
|
idlePlanets.remove(planetID) |
|
748
|
|
|
# remove this struct from possibility list |
|
749
|
|
|
structsToBuild[targetPlanetID][techID] -= 1 |
|
750
|
|
|
if structsToBuild[targetPlanetID][techID] == 0: |
|
751
|
|
|
del structsToBuild[targetPlanetID][techID] |
|
752
|
|
|
break |
|
753
|
|
|
return idlePlanets |
|
754
|
|
|
|
|
755
|
|
|
def getStructBuildEffectivity(client, db, planetID, targetIDs, structsToBuild, structsToDemo): |
|
756
|
|
|
""" Tries to sort all possible projects given the limits by parameters by |
|
757
|
|
|
their CP-wise effectivity |
|
758
|
|
|
|
|
759
|
|
|
targetIDs - iterable of all planets, on which are defined structsToBuild, |
|
760
|
|
|
and structsToDemo. |
|
761
|
|
|
structToBuild, structsToDemo - dictionaries, keys are planetIDs of players |
|
762
|
|
|
or free planets, and values are dictionaries with keys |
|
763
|
|
|
being techIDs and values being number of those structs. |
|
764
|
|
|
|
|
765
|
|
|
Returns list of tuples (techID, targetPlanetID, targetTechID), sorted by |
|
766
|
|
|
how long it takes to "pay back itself" from fastest to longest. Negative |
|
767
|
|
|
values are valued differently, but order is made with same principle. |
|
768
|
|
|
|
|
769
|
|
|
""" |
|
770
|
|
|
planet = db[planetID] |
|
771
|
|
|
player = client.getPlayer() |
|
772
|
|
|
possibilitiesBuild = {} |
|
773
|
|
|
for targetID in targetIDs: |
|
774
|
|
|
target = db[targetID] |
|
775
|
|
|
if planetID == targetID: |
|
776
|
|
|
coeff = 1 |
|
777
|
|
|
else: |
|
778
|
|
|
coeff = 2 |
|
779
|
|
|
# if build on empty slot |
|
780
|
|
|
for techID in structsToBuild[targetID]: |
|
781
|
|
|
tech = client.getFullTechInfo(techID) |
|
782
|
|
|
techEff = Rules.techImprEff[player.techs.get(techID, 1)] |
|
783
|
|
|
eff = float(tech.prodProd) * techEff / (tech.buildProd * coeff) * sum([x*y/100.0 for x, y in zip(tech.prodProdMod, [target.plBio, target.plMin, target.plEn, 100])]) |
|
784
|
|
|
eff = round(eff, 3) |
|
785
|
|
|
try: |
|
786
|
|
|
possibilitiesBuild[eff].append((techID, targetID, Const.OID_NONE)) |
|
787
|
|
|
except KeyError: |
|
788
|
|
|
possibilitiesBuild[eff] = [(techID, targetID, Const.OID_NONE)] |
|
789
|
|
|
# if build over the another structure |
|
790
|
|
|
for targTechID in structsToDemo[targetID]: |
|
791
|
|
|
targTech = client.getFullTechInfo(targTechID) |
|
792
|
|
|
targTechEff = Rules.techImprEff[player.techs.get(targTechID, 1)] |
|
793
|
|
|
prod = targTech.prodProd * targTechEff * sum([x*y/100.0 for x, y in zip(targTech.prodProdMod, [target.plBio, target.plMin, target.plEn, 100])]) |
|
794
|
|
|
for techID in structsToBuild[targetID]: |
|
795
|
|
|
tech = client.getFullTechInfo(techID) |
|
796
|
|
|
techEff = Rules.techImprEff[player.techs.get(techID, 1)] |
|
797
|
|
|
finalProd = float(tech.prodProd) * techEff - prod |
|
798
|
|
|
if finalProd > 0: |
|
799
|
|
|
eff = finalProd / (tech.buildProd * coeff) * sum([x*y/100.0 for x, y in zip(tech.prodProdMod, [target.plBio, target.plMin, target.plEn, 100])]) |
|
800
|
|
|
# negative values are handled separately, as division creates illogical coefficient |
|
801
|
|
|
else: |
|
802
|
|
|
eff = finalProd * tech.buildProd * coeff * sum([x*y/100.0 for x, y in zip(tech.prodProdMod, [target.plBio, target.plMin, target.plEn, 100])]) |
|
803
|
|
|
eff = round(eff, 3) |
|
804
|
|
|
try: |
|
805
|
|
|
possibilitiesBuild[eff].append((techID, targetID, targTechID)) |
|
806
|
|
|
except KeyError: |
|
807
|
|
|
possibilitiesBuild[eff] = [(techID, targetID, targTechID)] |
|
808
|
|
|
toBuild = [] |
|
809
|
|
|
toDemo = [] |
|
810
|
|
|
for infoTuple in [possibilitiesBuild[x] for x in sorted(possibilitiesBuild, reverse=True)]: |
|
811
|
|
|
toBuild += infoTuple |
|
812
|
|
|
return toBuild |
|
813
|
|
|
|
|
814
|
|
|
def compareBuildStructPlans(plan1, plan2): |
|
815
|
|
|
""" Compare both dictionaries. Only difference from normal comparison |
|
816
|
|
|
is that not having key is the same, as having key with value 0. |
|
817
|
|
|
|
|
818
|
|
|
Returns Bool value |
|
819
|
|
|
|
|
820
|
|
|
""" |
|
821
|
|
|
plan1Keys = set(plan1.keys()) |
|
822
|
|
|
plan2Keys = set(plan2.keys()) |
|
823
|
|
|
for key in plan1Keys - plan2Keys: |
|
824
|
|
|
if plan1[key]: |
|
825
|
|
|
return False |
|
826
|
|
|
for key in plan2Keys - plan1Keys: |
|
827
|
|
|
if plan2[key]: |
|
828
|
|
|
return False |
|
829
|
|
|
for key in plan1Keys & plan2Keys: |
|
830
|
|
|
if not plan1[key] == plan2[key]: |
|
831
|
|
|
return False |
|
832
|
|
|
return True |
|
833
|
|
|
|
|
834
|
|
|
|