Completed
Pull Request — master (#225)
by Marek
01:43
created

IGalaxy._setupEnvironmentPirate()   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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