ige.ospace.IGalaxy.IGalaxy.processACTIONPhase()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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