Completed
Push — master ( a0c806...0e659f )
by Marek
16s queued 13s
created

IGalaxy._setupEnvironmentUniquePerPlanet()   A

Complexity

Conditions 2

Size

Total Lines 10
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
nop 5
dl 0
loc 10
rs 9.9
c 0
b 0
f 0
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
import time
23
import copy
24
import random
25
26
from xml.dom.minidom import Node, parse
27
28
import ige
29
from IAIPlayer import IAIPlayer
30
from IAIEDENPlayer import IAIEDENPlayer
31
from IAIMutantPlayer import IAIMutantPlayer
32
from IAIPiratePlayer import IAIPiratePlayer
33
from IAIRenegadePlayer import IAIRenegadePlayer
34
import Const
35
import Rules
36
import Scanner
37
import Utils
38
39
from ige import log
40
from ige.IObject import IObject
41
from ige.IDataHolder import IDataHolder
42
from ige.IObject import public
43
from ISystem import ISystem
44
from Rules import Tech
45
46
47
class IGalaxy(IObject):
48
49
    typeID = Const.T_GALAXY
50
    forums = {"PUBLIC": 112, "NEWS": 112}
51
52
    def init(self, obj):
53
        IObject.init(self, obj)
54
        #
55
        obj.name = ""
56
        obj.owner = Const.OID_NONE
57
        obj.x = 0.0
58
        obj.y = 0.0
59
        obj.radius = 0.0
60
        obj.centerWeight = 250.0
61
        obj.systems = []
62
        obj.startingPos = []
63
        obj.numOfStartPos = 0
64
        obj.timeEnabled = None # none instead of False, to know when first enablement is happening
65
        obj.timePaused = False # this is only used for player-initiated pause, prevents autoenablement
66
        obj.creationTurn = 0
67
        obj.imperator = Const.OID_NONE
68
        obj.description = ""
69
        obj.scenario = Const.SCENARIO_NONE
70
        obj.scenarioData = IDataHolder()
71
        # electromagnetic radiation
72
        obj.emrLevel = 1.0
73
        obj.emrTrend = 1.0
74
        obj.emrTime = 0
75
        # galaxy keeps track of it's own time as well (because of pauses)
76
        obj.galaxyTurn = 0
77
78
    def update(self, tran, obj):
79
        # check existence of all systems
80
        if 0:
81
            for systemID in obj.systems:
82
                if not tran.db.has_key(systemID):
83
                    log.debug("CONSISTENCY - system %d from galaxy %d does not exists" % (systemID, obj.oid))
84
                elif tran.db[systemID].type not in (Const.T_SYSTEM, Const.T_WORMHOLE):
85
                    log.debug("CONSISTENCY - system %d from galaxy %d is not a Const.T_SYSTEM or Const.T_WORMHOLE" % (systemID, obj.oid))
86
        # validate starting positions
87
        for planetID in obj.startingPos[:]:
88
            if not tran.db.has_key(planetID):
89
                log.debug("REMOVING nonexistent obj from start pos", planetID)
90
                obj.startingPos.remove(planetID)
91
                continue
92
            planet = tran.db[planetID]
93
            if planet.type != Const.T_PLANET:
94
                log.debug("REMOVING ??? from start pos", planetID)
95
                obj.startingPos.remove(planetID)
96
        # check compOf
97
        if not tran.db.has_key(obj.compOf) or tran.db[obj.compOf].type != Const.T_UNIVERSE:
98
            log.debug("CONSISTENCY invalid compOf for galaxy", obj.oid, obj.compOf)
99
        # TODO: remove after 0.5.73
100
        if not hasattr(obj, 'galaxyTurn'):
101
            obj.galaxyTurn = 0
102
        obj.timeEnabled = bool(obj.timeEnabled)
103
104
105
    update.public = 0
106
107
    def getReferences(self, tran, obj):
108
        return obj.systems
109
110
    getReferences.public = 0
111
112
    @staticmethod
113
    def getFreeStartingPosition(db, obj):
114
        while 1:
115
            planetID = random.choice(obj.startingPos)
116
            obj.startingPos.remove(planetID)
117
            log.debug('Starting point', planetID)
118
            log.debug('Starting point - owner', db[planetID].owner)
119
            if db[planetID].owner == Const.OID_NONE:
120
                return planetID
121
            if not obj.startingPos:
122
                raise ige.GameException('No free starting point in the galaxy.')
123
124
    @public(Const.AL_ADMIN)
125
    def processINITPhase(self, tran, obj, data):
126
        if not obj.timeEnabled:
127
            return
128
        # compute emr level
129
        turn = tran.db[Const.OID_UNIVERSE].turn
130
        # galaxy keeps track of it's own time (because of pauses)
131
        obj.galaxyTurn += 1
132
        obj.emrTime -= 1
133
        if obj.emrTime <= 0:
134
            modulo = turn % Rules.emrPeriod
135
            for season in Rules.emrSeasons:
136
                if modulo >= season.startTime and modulo <= season.endTime:
137
                    log.debug("EMR - season", season.name)
138
                    obj.emrTrend = Utils.rand(int(season.emrLevelMin * 100), int(season.emrLevelMax * 100) + 1) / 100.0
139
                    obj.emrTime = Utils.rand(Rules.emrMinDuration, Rules.emrMaxDuration)
140
                    log.debug("EMR - trend, time", obj.emrTrend, obj.emrTime)
141
                    message = {
142
                        "sender": "GNC",
143
                        "senderID": obj.oid,
144
                        "forum": "NEWS",
145
                        "data": (obj.oid, Const.MSG_GNC_EMR_FORECAST, obj.oid, turn, (obj.emrTrend, obj.emrTime)),
146
                        "topic": "EVENT",
147
                    }
148
                    self.cmd(obj).sendMsg(tran, obj, message)
149
                    break
150
        elif obj.emrLevel >= obj.emrTrend:
151
            obj.emrLevel -= Utils.rand(1, 6) / 100.0
152
        elif obj.emrLevel <= obj.emrTrend:
153
            obj.emrLevel += Utils.rand(1, 6) / 100.0
154
        # remove old messages
155
        self.cmd(obj).deleteOldMsgs(tran, obj)
156
        return obj.systems
157
158
    @public(Const.AL_ADMIN)
159
    def processPRODPhase(self, tran, obj, data):
160
        if not obj.timeEnabled:
161
            return
162
        return obj.systems
163
164
    @public(Const.AL_ADMIN)
165
    def processACTIONPhase(self, tran, obj, data):
166
        if not obj.timeEnabled:
167
            return
168
        return obj.systems
169
170
    @public(Const.AL_ADMIN)
171
    def processSCAN2Phase(self, tran, obj, data):
172
        # data == True means forced scan (first after generating the galaxy)
173
        if not obj.timeEnabled and not data:
174
            return
175
        # compute scanner for all objects on the map
176
        playerMap = Scanner.computeMap(self, tran, obj)
177
        # distribute map
178
        for playerID, map in playerMap.iteritems():
179
            player = tran.db[playerID]
180
            self.cmd(player).mergeScannerMap(tran, player, map)
181
        return
182
183
    @public(Const.AL_ADMIN)
184
    def processBATTLEPhase(self, tran, obj, data):
185
        if not obj.timeEnabled:
186
            return
187
        return obj.systems
188
189
    @public(Const.AL_ADMIN)
190
    def processFINALPhase(self, tran, obj, data):
191
        if not obj.timeEnabled:
192
            return
193
        # validate starting positions
194
        remove = []
195
        for planetID in obj.startingPos:
196
            planet = tran.db[planetID]
197
            if planet.owner != Const.OID_NONE:
198
                remove.append(planetID)
199
        for planetID in remove:
200
            obj.startingPos.remove(planetID)
201
        return obj.systems
202
203
    @public(Const.AL_ADMIN)
204
    def processFINAL2Phase(self, tran, obj, data):
205
        if not obj.timeEnabled:
206
            return
207
        # save history file
208
        turn = tran.db[Const.OID_UNIVERSE].turn
209
        # TODO: reneable history when it's optimized
210
        if turn % 6 == 0 and False:
211
            log.debug("Saving history for galaxy", obj.oid, obj.name)
212
            fh = open(os.path.join(tran.config.configDir,"history/galaxy%d-%06d.xml" % (obj.oid, turn), "w+"))
213
            print >>fh, '<?xml version="1.0" encoding="UTF-8"?>'
214
            print >>fh, '<history turn="%d" galaxy="%d" name="%s">' % (turn, obj.oid, obj.name)
215
            # save systems and owners
216
            players = {}
217
            print >>fh, '  <systems>'
218
            for systemID in obj.systems:
219
                system = tran.db[systemID]
220
                owners = {}
221
                for planetID in system.planets:
222
                    ownerID = tran.db[planetID].owner
223
                    if ownerID != Const.OID_NONE:
224
                        owners[ownerID] = tran.db[ownerID].name
225
                        players[ownerID] = None
226
                print >>fh, '    <sys x="%.2f" y="%.2f" name="%s" owners="%s"/>' % (
227
                    system.x,
228
                    system.y,
229
                    system.name,
230
                    ",".join(owners.values())
231
                )
232
            print >>fh, '  </systems>'
233
            # stats
234
            print >>fh, '  <stats>'
235
            for playerID in players:
236
                player = tran.db[playerID]
237
                print >>fh, '    <pl name="%s" pop="%d" planets="%d" stucts="%d" cp="%d" mp="%d" rp="%d"/>'% (
238
                    player.name,
239
                    player.stats.storPop,
240
                    player.stats.planets,
241
                    player.stats.structs,
242
                    player.stats.prodProd,
243
                    player.stats.fleetPwr,
244
                    player.stats.prodSci,
245
                )
246
            print >>fh, '  </stats>'
247
            print >>fh, '</history>'
248
249
250
    @public(Const.AL_ADMIN)
251
    def loadFromXML(self, tran, obj, file, galaxyType, x, y, name):
252
        log.message('IGalaxy', 'Parsing XML file...')
253
        dom = parse(os.path.join('data', file))
254
        log.message('IGalaxy', 'XML file parsed.')
255
        assert dom.documentElement.tagName == 'universe'
256
        for node in dom.documentElement.childNodes:
257
            if node.nodeType == Node.ELEMENT_NODE and node.tagName == 'galaxy':
258
                if node.getAttribute('galaxyType') == galaxyType:
259
                    self.loadDOMNode(tran, obj, node, x, y, name)
260
                    self.connectWormHoles(tran, obj)
261
                    return Const.SUCC
262
        raise ige.GameException('No such id %s in resource' % galaxyType)
263
264
    def loadDOMNode(self, tran, obj, node, x, y, name):
265
        obj.name = name
266
        obj.x = float(x)
267
        obj.y = float(y)
268
        xoff = x - float(node.getAttribute('x'))
269
        yoff = y - float(node.getAttribute('y'))
270
        obj.creationTurn = tran.db[Const.OID_UNIVERSE].turn
271
        for elem in node.childNodes:
272
            if elem.nodeType == Node.ELEMENT_NODE:
273
                name = elem.tagName
274
                if name == 'properties':
275
                    self.loadDOMAttrs(obj, elem)
276
                elif name == 'system':
277
                    system = tran.db[self.createSystem(tran, obj)]
278
                    self.cmd(system).loadDOMNode(tran, system, xoff, yoff, elem)
279
                elif name == 'hole':
280
                    wormHole = tran.db[self.createWormHole(tran, obj)]
281
                    self.cmd(wormHole).loadDOMNode(tran, wormHole, xoff, yoff, elem)
282
                else:
283
                    raise ige.GameException('Unknown element %s' % name)
284
        return Const.SUCC
285
286
    def connectWormHoles(self, tran, obj):
287
        wormHoles = {}
288
        for holeID in obj.systems:
289
            wormHole = tran.db[holeID]
290
            if wormHole.type == Const.T_WORMHOLE:
291
                wormHoles[wormHole.name] = holeID
292
293
        for holeID in obj.systems:
294
            wormHole = tran.db[holeID]
295
            if wormHole.type != Const.T_WORMHOLE:
296
                continue
297
            if len(wormHole.destination) == 0:
298
                raise ige.GameException('Wrong WormHole(%d) definition' % holeID)
299
            if wormHole.destination == wormHole.name:
300
                raise ige.GameException('Same destination as position for WormHole(%d)' % holeID)
301
            destinationOid = wormHoles[wormHole.destination]
302
            if destinationOid == Const.OID_NONE:
303
                raise ige.GameException('WormHole(%d) has wrong destination ''%s''' % (holeID, wormHole.destination))
304
            wormHole.destinationOid = destinationOid
305
306
    def createSystem(self, tran, obj):
307
        system = self.new(Const.T_SYSTEM)
308
        system.compOf = obj.oid
309
        oid = tran.db.create(system)
310
        obj.systems.append(oid)
311
        return oid
312
313
    def createWormHole(self, tran, galaxy):
314
        hole = self.new(Const.T_WORMHOLE)
315
        hole.compOf = galaxy.oid
316
        oid = tran.db.create(hole)
317
        galaxy.systems.append(oid)
318
        return oid
319
320
    @public(Const.AL_OWNER)
321
    def toggleTime(self, tran, obj):
322
        player = tran.db[obj.owner]
323
        obj.timeEnabled = not obj.timeEnabled
324
        obj.timePaused = not obj.timeEnabled
325
        self._trickleTimeToPlayers(tran, obj)
326
        return obj.timeEnabled
327
328
    def _trickleTimeToPlayers(self, tran, obj):
329
        # enable time for players
330
        playerIDs = set()
331
        for systemID in obj.systems:
332
            system = tran.db[systemID]
333
            for planetID in system.planets:
334
                planet = tran.db[planetID]
335
                playerIDs.add(planet.owner)
336
        playerIDs.discard(Const.OID_NONE)
337
        for playerID in playerIDs:
338
            player = tran.db[playerID]
339
            if player.timeEnabled != obj.timeEnabled:
340
                player.timeEnabled = obj.timeEnabled
341
                player.lastLogin = time.time()
342
                if player.timeEnabled:
343
                    Utils.sendMessage(tran, player, Const.MSG_ENABLED_TIME, player.oid, None)
344
                else:
345
                    Utils.sendMessage(tran, player, Const.MSG_DISABLED_TIME, player.oid, None)
346
347
    def _isEligibleEnableTime(self, tran, obj):
348
        if obj.timeEnabled or obj.timePaused:
349
            # explicitly paused galaxy needs to be explicitly unpaused
350
            return False
351
        # We have to give players some time to prepare
352
        # (as they might be waiting for very long time for this galaxy to be created).
353
        currentTurn = tran.db[Const.OID_UNIVERSE].turn
354
        if obj.creationTurn + Rules.galaxyStartDelay <= currentTurn:
355
            log.debug("Time to prepare has passed", obj.creationTurn, currentTurn)
356
            return True
357
        elif obj.scenario == Const.SCENARIO_SINGLE:
358
            return True
359
        return False
360
361
    def _firstEnableTime(self, tran, obj):
362
        # spawn rebel player on all vacant starting positions
363
        for positionID in copy.copy(obj.startingPos):
364
            obj.startingPos.remove(positionID)
365
            # create new player
366
            log.debug("Creating new Rebel player", Const.T_AIPLAYER)
367
            player = self.new(Const.T_AIPLAYER)
368
            self.cmd(player).register(tran, player, obj.oid)
369
            player.galaxy = obj.oid
370
            playerID = player.oid
371
            # TODO tweak more planet's attrs
372
            planet = tran.db[positionID]
373
            self.cmd(planet).changeOwner(tran, planet, playerID, 1)
374
            IAIPlayer.setStartingTechnologies(player)
375
            # fleet
376
            # add basic ships designs
377
            # add small fleet
378
            system = tran.db[planet.compOf]
379
            IAIPlayer.setStartingShipDesigns(player)
380
            IAIPlayer.setStartingPlanet(tran, playerID, planet)
381
            IAIPlayer.setStartingFleet(tran, playerID, system)
382
            system.scannerPwrs[playerID] = Rules.startingScannerPwr
383
        # do scanner evaluation because of all new players
384
        self.cmd(obj).processSCAN2Phase(tran, obj, None)
385
386
    @public(Const.AL_ADMIN)
387
    def enableTime(self, tran, obj, force = False):
388
        log.debug('IGalaxy', 'Checking for time...')
389
        if not force and not self._isEligibleEnableTime(tran, obj):
390
            return
391
        if obj.timeEnabled is None:
392
            self._firstEnableTime(tran, obj)
393
        # ok, enable time
394
        log.message('IGalaxy', 'Enabling time for', obj.oid)
395
        obj.timeEnabled = True
396
        self._trickleTimeToPlayers(tran, obj)
397
398
    @public(Const.AL_OWNER)
399
    def deleteSingle(self, tran, obj):
400
        if obj.scenario != Const.SCENARIO_SINGLE:
401
            raise ige.GameException('Only Single Player galaxies can be deleted this way')
402
        log.debug(obj.oid, "GALAXY - singleplayer delete")
403
        self.delete(tran, obj)
404
405
    @public(Const.AL_ADMIN)
406
    def delete(self, tran, obj):
407
        log.debug(obj.oid, "GALAXY - delete")
408
        universe = tran.db[Const.OID_UNIVERSE]
409
        # delete systems and planets
410
        for systemID in obj.systems:
411
            log.debug("Deleting system", systemID)
412
            system = tran.db[systemID]
413
            log.debug("-- planets", system.planets)
414
            log.debug("-- fleets", system.fleets, system.closeFleets)
415
            for planetID in system.planets[:]:
416
                planet = tran.db[planetID]
417
                self.cmd(planet).changeOwner(tran, planet, Const.OID_NONE, force = 1)
418
                del tran.db[planetID]
419
            for fleetID in system.closeFleets[:]:
420
                fleet = tran.db[fleetID]
421
                # this will modify system fleet and closeFleets attrs
422
                self.cmd(fleet).disbandFleet(tran, fleet)
423
            del tran.db[systemID]
424
        # delete all remaining fleets
425
        for playerID in universe.players[:]:
426
            player = tran.db[playerID]
427
            if obj.oid != player.galaxy:
428
                continue
429
            if player.fleets:
430
                log.debug("Player %d has still fleets" % playerID, player.name, player.fleets)
431
                for fleetID in player.fleets:
432
                    fleet = tran.db[fleetID]
433
                    log.debug("Fleet NOT DELETED:", fleet)
434
            if player.planets:
435
                log.debug("Player %d has still planets" % playerID, player.name, player.planets)
436
            self.cmd(player).delete(tran, player)
437
        # remove this galaxy from the list of the galaxies
438
        tran.db[Const.OID_UNIVERSE].galaxies.remove(obj.oid)
439
        del tran.db[obj.oid]
440
        return 1
441
442
    @public(Const.AL_NONE)
443
    def getPublicInfo(self, tran, obj):
444
        result = IDataHolder()
445
        result.oid = obj.oid
446
        result.x = obj.x
447
        result.y = obj.y
448
        result.radius = obj.radius
449
        result.type = obj.type
450
        result.name = obj.name
451
        result.emrLevel = obj.emrLevel
452
        result.scenario = obj.scenario
453
        result.scenarioData = obj.scenarioData
454
        result.timeEnabled = obj.timeEnabled
455
        return result
456
457
    @public(Const.AL_NONE)
458
    def getDescription(self,obj):
459
        return obj.description
460
461
    def _environmentGetVacantPlanets(self, tran, obj):
462
        vacantPlanets = {}
463
        for systemID in obj.systems:
464
            system = tran.db[systemID]
465
            for planetID in system.planets:
466
                planet = tran.db[planetID]
467
                if planet.owner != Const.OID_NONE:
468
                    continue
469
                if planet.plStratRes in (Const.SR_TL1A, Const.SR_TL1B):
470
                    playerType = Const.T_AIRENPLAYER
471
                elif planet.plStratRes in (Const.SR_TL3A, Const.SR_TL3B, Const.SR_TL3C):
472
                    playerType = Const.T_AIPIRPLAYER
473
                elif planet.plStratRes in (Const.SR_TL5A, Const.SR_TL5B, Const.SR_TL5C):
474
                    playerType = Const.T_AIEDENPLAYER
475
                elif planet.plDisease != 0:
476
                    playerType = Const.T_AIMUTPLAYER
477
                else:
478
                    continue
479
                try:
480
                    vacantPlanets[playerType].append(planetID)
481
                except KeyError:
482
                    vacantPlanets[playerType] = [planetID]
483
        return vacantPlanets
484
485
    def _searchForPlayer(self, tran, obj, playerType):
486
        universe = tran.db[Const.OID_UNIVERSE]
487
        for playerID in universe.players:
488
            player = tran.db[playerID]
489
            if obj.oid == player.galaxy and player.type == playerType:
490
                return player
491
        # create new player
492
        log.debug("Creating new player", playerType)
493
        player = self.new(playerType)
494
        self.cmd(player).register(tran, player, obj.oid)
495
        player.galaxy = obj.oid
496
        return player
497
498
    def _setupEnvironmentUniquePerPlanet(self, tran, obj, vacantPlanets, aiClass):
499
        planets = vacantPlanets.pop(aiClass.typeID)
500
        for planetID in planets:
501
            planet = tran.db[planetID]
502
            log.debug("Creating new ai, type", aiClass.typeID)
503
            player = self.new(aiClass.typeID)
504
            self.cmd(player).register(tran, player, obj.oid)
505
            player.galaxy = obj.oid
506
            self.cmd(planet).changeOwner(tran, planet, player.oid, 1)
507
            aiClass.setStartingPlanet(tran, planet)
508
509
    @public(Const.AL_ADMIN)
510
    def setupEnvironment(self, tran, obj):
511
        vacantPlanets = self._environmentGetVacantPlanets(tran, obj)
512
        self._setupEnvironmentUniquePerPlanet(tran, obj, vacantPlanets, IAIRenegadePlayer)
513
        self._setupEnvironmentUniquePerPlanet(tran, obj, vacantPlanets, IAIMutantPlayer)
514
        # iterate over types, create players if needed (it should be) and fill in vacant planets
515
        for playerType in vacantPlanets:
516
            player = self._searchForPlayer(tran, obj, playerType)
517
            # now we have a player, let's iterate over vacant planets and set them up
518
            for planetID in vacantPlanets[playerType]:
519
                planet = tran.db[planetID]
520
                self.cmd(planet).changeOwner(tran, planet, player.oid, 1)
521
                if playerType == Const.T_AIPIRPLAYER:
522
                    IAIPiratePlayer.setStartingPlanet(tran, planet)
523
                elif playerType == Const.T_AIEDENPLAYER:
524
                    IAIEDENPlayer.setStartingPlanet(tran, planet)
525
526
    ## messaging
527
    def canGetMsgs(self, tran, obj, oid):
528
        return 1
529
530
    canGetMsgs.public = 0
531
532
    def canSendMsg(self, tran, obj, oid, forum):
533
        if forum == "PUBLIC":
534
            return 1
535
        elif forum == "NEWS":
536
            return 1
537
        return 0
538
539
    canSendMsg.public = 0
540