NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

structure.py (29374B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\structures\structure.py
      2 import datetime
      3 from random import randint
      4 import signals
      5 import structures
      6 from structures import reinforce
      7 import logging
      8 logger = logging.getLogger(__name__)
      9 
     10 class Structure(object):
     11 
     12     def __init__(self):
     13         self.state = structures.STATE_UNKNOWN
     14         self.damage = (None, None, None)
     15         self.vulnerable = None
     16         self.firstAggressed = None
     17         self.lastAggressed = None
     18         self.notificationSent = False
     19         self.repairing = None
     20         self.unanchoring = None
     21         self.reinforceTiming = reinforce.ReinforceTiming()
     22         self.upkeepState = None
     23         self.abandonAt = None
     24         self.countdownTimer = CountdownTimer(self)
     25         self.deployStartTime = None
     26         self.deployEndTime = None
     27         self.structureSize = structures.STRUCTURE_SIZE_UNDEFINED
     28         self.deedState = None
     29         self.hangarView = None
     30         self.OnStateChanged = signals.Signal(signalName='OnStateChanged')
     31         self.OnDamageChanged = signals.Signal(signalName='OnDamageChanged')
     32         self.OnVulnerabilityChanged = signals.Signal(signalName='OnVulnerabilityChanged')
     33         self.OnRepairingChanged = signals.Signal(signalName='OnRepairingChanged')
     34         self.OnDeployTimeChanged = signals.Signal(signalName='OnDeployTimeChanged')
     35         self.OnAggressionChanged = signals.Signal(signalName='OnAggressionChanged')
     36         self.OnUnanchoringChanged = signals.Signal(signalName='OnUnanchoringChanged')
     37         self.OnFirstAggressed = signals.Signal(signalName='OnFirstAggressed')
     38         self.OnRepairCompleted = signals.Signal(signalName='OnRepairCompleted')
     39         self.OnReinforceTimeChanged = signals.Signal(signalName='OnReinforceTimeChanged')
     40 
     41     def IsFlexStructure(self):
     42         return self.structureSize == structures.STRUCTURE_SIZE_FLEX
     43 
     44     def IsMediumStructure(self):
     45         return self.structureSize == structures.STRUCTURE_SIZE_MEDIUM
     46 
     47     def Update(self):
     48         self.UpdateVulnerability()
     49         self.UpdateState()
     50         self.UpdateTimers()
     51         self.UpdateRepair()
     52         self.UpdateUnanchoring()
     53         self.UpdateReinforceTimer()
     54         self.UpdateUpkeep()
     55         self.UpdateDeed()
     56 
     57     def SaveState(self, state):
     58         pass
     59 
     60     def SaveDamage(self, damage):
     61         pass
     62 
     63     def SaveVulnerable(self, vulnerable):
     64         pass
     65 
     66     def SaveReinforceTimes(self):
     67         pass
     68 
     69     def SaveTimer(self, start, end, paused):
     70         pass
     71 
     72     def SaveRepairing(self, repairing):
     73         pass
     74 
     75     def SaveUnanchoring(self, unanchoring):
     76         pass
     77 
     78     def _SaveDeployTimer(self, start, end):
     79         pass
     80 
     81     def SaveAbandoned(self):
     82         pass
     83 
     84     def SaveUpkeepState(self):
     85         pass
     86 
     87     def GetRepairTime(self):
     88         return structures.STATE_REPAIR_TIME.get(self.state, structures.DEFAULT_REPAIR_TIMER)
     89 
     90     def GetReinforceDurationArmor(self):
     91         return structures.REINFORCE_DURATION_ARMOR_DEFAULT
     92 
     93     def GetReinforceDurationHull(self):
     94         return structures.REINFORCE_DURATION_HULL_DEFAULT
     95 
     96     def ApplyJitterToReinforceExitTime(self, exitTime):
     97         if self.IsFlexStructure():
     98             jitterRange = structures.REINFORCE_EXIT_JITTER_FLEX_SECONDS
     99         elif self.IsMediumStructure():
    100             jitterRange = structures.REINFORCE_EXIT_JITTER_MEDIUM_SECONDS
    101         elif self.IsInsurgencyFOB():
    102             jitterRange = structures.REINFORCE_EXIT_JITTER_INSURGENCY_FOB_SECONDS
    103         else:
    104             jitterRange = structures.REINFORCE_EXIT_JITTER_DEFAULT_SECONDS
    105         return exitTime + reinforce.GenerateJitterTimeDelta(jitterRange)
    106 
    107     def GenerateArmorReinforceExitDateTime(self, startDateTime):
    108         duration = self.GetReinforceDurationArmor()
    109         exitTime = startDateTime + datetime.timedelta(seconds=duration)
    110         exitTime = self.reinforceTiming.GetEarliestTimeAfterDatetime(exitTime)
    111         exitTime = self.ApplyJitterToReinforceExitTime(exitTime)
    112         return exitTime
    113 
    114     def GenerateHullReinforceExitDateTime(self, startDateTime):
    115         if self.IsFlexStructure():
    116             duration = structures.FLEX_REINFORCE_DURATION_HULL
    117         else:
    118             duration = self.GetReinforceDurationHull()
    119         exitTime = startDateTime + datetime.timedelta(seconds=duration)
    120         exitTime = self.reinforceTiming.GetEarliestTimeAfterDatetime(exitTime)
    121         exitTime = self.ApplyJitterToReinforceExitTime(exitTime)
    122         return exitTime
    123 
    124     def GetAnchoringTime(self):
    125         return structures.ANCHORING_TIME
    126 
    127     def GetFlexDeployingTime(self):
    128         return structures.FLEX_DEPLOYING_TIME
    129 
    130     def GetUnanchoringTime(self):
    131         return structures.UNANCHORING_TIME
    132 
    133     def HandleStateChanged(self):
    134         self.countdownTimer.ClearTimer()
    135         self.SetDeployingTimer(None, None)
    136         self.SetRepairing(None)
    137         self.UpdateVulnerability()
    138         self.UpdateTimers()
    139         self.SetAggression(None)
    140         self.UpdateShieldArmorHull()
    141         self.UpdateRepair()
    142         self.UpdateUnanchoring()
    143         self.UpdateReinforceTimer()
    144 
    145     def HandleDamageChanged(self):
    146         self.UpdateAggression()
    147         self.Update()
    148 
    149     def UpdateShieldArmorHull(self, *args):
    150         if self.state in structures.STATE_SHIELD_ARMOR_HULL:
    151             self.SetDamage(*structures.STATE_SHIELD_ARMOR_HULL[self.state])
    152 
    153     def UpdateVulnerability(self, *args):
    154         if self.state in structures.STATE_VULNERABILITY:
    155             self.SetVulnerable(structures.STATE_VULNERABILITY[self.state])
    156 
    157     def UpdateState(self, *args):
    158         if self.state == structures.STATE_SHIELD_VULNERABLE:
    159             if self.IsArmorDamaged() and self.CanHaveArmorPhases():
    160                 self.PlaySpecialFX('effects.StructureRepairStateFailed')
    161                 if self._CanHaveArmorReinforce():
    162                     self.SetState(structures.STATE_ARMOR_REINFORCE)
    163                 else:
    164                     self.SetState(structures.STATE_ARMOR_VULNERABLE)
    165             elif self.IsHullDamaged():
    166                 self.PlaySpecialFX('effects.StructureRepairStateFailed')
    167                 if self._CanHaveHullReinforce():
    168                     self.SetState(structures.STATE_HULL_REINFORCE)
    169                 else:
    170                     self.SetState(structures.STATE_HULL_VULNERABLE)
    171         elif self.state == structures.STATE_ARMOR_REINFORCE:
    172             if self.countdownTimer.IsExpired():
    173                 self.SetState(structures.STATE_ARMOR_VULNERABLE)
    174         elif self.state == structures.STATE_ARMOR_VULNERABLE:
    175             if not self.IsDamaged():
    176                 self.SetState(structures.STATE_SHIELD_VULNERABLE)
    177             elif self.IsHullDamaged():
    178                 self.PlaySpecialFX('effects.StructureRepairStateFailed')
    179                 if self._CanHaveHullReinforce():
    180                     self.SetState(structures.STATE_HULL_REINFORCE)
    181                 else:
    182                     self.SetState(structures.STATE_HULL_VULNERABLE)
    183         elif self.state == structures.STATE_HULL_REINFORCE:
    184             if self.countdownTimer.IsExpired():
    185                 self.SetState(structures.STATE_HULL_VULNERABLE)
    186         elif self.state == structures.STATE_HULL_VULNERABLE:
    187             if self.IsShieldOrArmorFullyHealed():
    188                 self.SetState(structures.STATE_SHIELD_VULNERABLE)
    189         elif self.state == structures.STATE_ANCHOR_VULNERABLE:
    190             if not self.IsDamaged():
    191                 self.SetState(structures.STATE_ANCHORING)
    192         elif self.state == structures.STATE_ANCHORING:
    193             if self.countdownTimer.IsExpired():
    194                 self.SetState(structures.STATE_ONLINING_VULNERABLE)
    195         elif self.state == structures.STATE_FITTING_INVULNERABLE:
    196             if self.countdownTimer.IsExpired():
    197                 self.PlaySpecialFX('effects.StructureRepairStateComplete')
    198                 self.SetState(structures.STATE_SHIELD_VULNERABLE)
    199         elif self.state == structures.STATE_ONLINING_VULNERABLE:
    200             if not self.IsDamaged() and self.HasDeedInSlot():
    201                 self.SetState(structures.STATE_SHIELD_VULNERABLE)
    202         elif self.state == structures.STATE_DEPLOY_VULNERABLE:
    203             if self.IsDeployTimeCompleted() and not self.IsHullDamaged():
    204                 self.SetState(structures.STATE_SHIELD_VULNERABLE)
    205         elif self.state == structures.STATE_ONLINE_DEPRECATED:
    206             self.SetState(structures.STATE_SHIELD_VULNERABLE)
    207 
    208     def _CanHaveArmorReinforce(self):
    209         if self.IsUpkeepState_Abandoned():
    210             return False
    211         if self.IsFlexStructure():
    212             return False
    213         if not self.IsMediumStructure() and self.IsUpkeepState_Low():
    214             return False
    215         return True
    216 
    217     def _CanHaveHullReinforce(self):
    218         if self.IsUpkeepState_Abandoned():
    219             return False
    220         if self.IsFlexStructure() and self.IsUpkeepState_Low():
    221             return False
    222         if self.IsMediumStructure():
    223             return False
    224         if self.IsInsurgencyFOB():
    225             return False
    226         return True
    227 
    228     def CanHaveArmorPhases(self):
    229         return True
    230 
    231     def IsDeployTimeCompleted(self):
    232         return self.deployEndTime and self.deployEndTime < self.GetTimeNow()
    233 
    234     def UpdateAggression(self, *args):
    235         if self.IsAggressed():
    236             aggressed = self.GetFirstAggressed()
    237             if not aggressed:
    238                 self.SetAggression(self.GetTimeNow(), self.GetTimeNow())
    239             else:
    240                 self.SetAggression(aggressed, self.GetTimeNow())
    241         else:
    242             self.SetAggression(None)
    243 
    244     def UpdateRepair(self, *args):
    245         if self.ShouldRepair():
    246             if self.GetRepairing() is None:
    247                 self.countdownTimer.SetTimer(self.GetTimeNow() + datetime.timedelta(seconds=self.GetRepairTime()), self.GetTimeNow())
    248                 self.SetRepairing(True)
    249             elif self.ShouldRepairPause():
    250                 self.countdownTimer.Pause()
    251                 self.SetRepairing(False)
    252             else:
    253                 self.countdownTimer.Unpause()
    254                 self.SetRepairing(True)
    255             if self.IsRepairing() and self.countdownTimer.IsExpired():
    256                 self.PlaySpecialFX('effects.StructureRepairStateComplete')
    257                 self.countdownTimer.ClearTimer()
    258                 if self.state == structures.STATE_DEPLOY_VULNERABLE:
    259                     self.RepairHullOnly()
    260                 else:
    261                     self.FullyRepairSelf()
    262                 self.OnRepairCompleted()
    263         else:
    264             self.SetRepairing(None)
    265 
    266     def UpdateTimers(self, *args):
    267         if self.ShouldRepair():
    268             return
    269         if self.state == structures.STATE_SHIELD_VULNERABLE:
    270             self.countdownTimer.ClearTimer()
    271         elif self.state == structures.STATE_ARMOR_REINFORCE:
    272             if not self.countdownTimer.IsRunning():
    273                 start = self.GetTimeNow()
    274                 self.countdownTimer.SetTimer(self.GenerateArmorReinforceExitDateTime(start))
    275         elif self.state == structures.STATE_ARMOR_VULNERABLE:
    276             self.countdownTimer.ClearTimer()
    277         elif self.state == structures.STATE_HULL_REINFORCE:
    278             if not self.countdownTimer.IsRunning():
    279                 start = self.GetTimeNow()
    280                 self.countdownTimer.SetTimer(self.GenerateHullReinforceExitDateTime(start))
    281         elif self.state == structures.STATE_HULL_VULNERABLE:
    282             self.countdownTimer.ClearTimer()
    283         elif self.state == structures.STATE_ANCHOR_VULNERABLE:
    284             self.countdownTimer.ClearTimer()
    285         elif self.state == structures.STATE_ANCHORING:
    286             if not self.countdownTimer.IsRunning():
    287                 self.countdownTimer.SetTimer(self.GetTimeNow() + datetime.timedelta(seconds=self.GetAnchoringTime()))
    288         elif self.state == structures.STATE_FITTING_INVULNERABLE:
    289             if not self.countdownTimer.IsRunning():
    290                 self.countdownTimer.SetTimer(self.GetTimeNow() + datetime.timedelta(seconds=structures.FITTING_INVULNERABLE_TIME))
    291         elif self.state == structures.STATE_ONLINING_VULNERABLE:
    292             self.countdownTimer.ClearTimer()
    293         elif self.state == structures.STATE_DEPLOY_VULNERABLE:
    294             if self.deployStartTime is None or self.deployEndTime is None:
    295                 self.SetDeployingTimer(self.GetTimeNow(), self.GetTimeNow() + datetime.timedelta(seconds=self.GetFlexDeployingTime()))
    296         elif self.state == structures.STATE_ONLINE_DEPRECATED:
    297             self.countdownTimer.ClearTimer()
    298         elif self.state == structures.STATE_FOB_INVULNERABLE:
    299             self.countdownTimer.ClearTimer()
    300 
    301     def SetDeployingTimer(self, startTime, endTime):
    302         if startTime == self.deployStartTime and endTime == self.deployEndTime:
    303             return
    304         self.deployStartTime = startTime
    305         self.deployEndTime = endTime
    306         self._SaveDeployTimer(self.deployStartTime, self.deployEndTime)
    307         self.OnDeployTimeChanged()
    308 
    309     def UpdateUnanchoring(self):
    310         if structures.STATE_CANCELS_UNANCHOR.get(self.state) is True:
    311             if self.GetUnanchoring() is not None and hasattr(self, 'LogUnanchoringForceCancelled'):
    312                 self.LogUnanchoringForceCancelled(self.state)
    313             self.SetUnanchoring(None)
    314         if self.HasUnanchoringExpired():
    315             self.SetState(structures.STATE_UNANCHORED)
    316 
    317     def UpdateReinforceTimer(self):
    318         if self.CanApplyNextReinforceTime():
    319             currentTime = self.GetTimeNow()
    320             if self.reinforceTiming.IsNextChangeReady(currentTime):
    321                 self.reinforceTiming.ApplyNextTiming()
    322                 self.SaveReinforceTimes()
    323                 self.OnReinforceTimeChanged(None, None)
    324 
    325     def GetState(self):
    326         return self.state
    327 
    328     def SetState(self, state):
    329         if state not in structures.STATES:
    330             raise structures.InvalidStructureState(state)
    331         current = self.GetState()
    332         if state != current:
    333             self.state = state
    334             self.SaveState(state)
    335             self.HandleStateChanged()
    336             self.OnStateChanged(self, current, state)
    337 
    338     def IsOnline(self):
    339         return self.GetState() not in structures.OFFLINE_STATES
    340 
    341     def IsOffline(self):
    342         return self.GetState() in structures.OFFLINE_STATES
    343 
    344     def IsDisabled(self):
    345         return self.GetState() in structures.DISABLED_STATES
    346 
    347     def GetUnanchoring(self):
    348         return self.unanchoring
    349 
    350     def SetUnanchoring(self, unanchoring):
    351         if unanchoring is not None and not isinstance(unanchoring, datetime.datetime):
    352             raise TypeError('Unanchoring time must be a datetime object or None')
    353         if unanchoring != self.GetUnanchoring():
    354             self.unanchoring = unanchoring
    355             self.SaveUnanchoring(unanchoring)
    356             self.OnUnanchoringChanged(self, unanchoring)
    357 
    358     def SetUnanchoringRemaining(self, seconds):
    359         self.SetUnanchoring(self.GetTimeNow() + datetime.timedelta(seconds=int(seconds)))
    360 
    361     def GetUnanchoringRemaining(self):
    362         unanchoring = self.GetUnanchoring()
    363         if unanchoring is not None:
    364             return int((unanchoring - self.GetTimeNow()).total_seconds())
    365 
    366     def StartUnanchoring(self):
    367         if self.GetUnanchoring() is None:
    368             self.SetUnanchoring(self.GetTimeNow() + datetime.timedelta(seconds=self.GetUnanchoringTime()))
    369 
    370     def StopUnanchoring(self):
    371         if self.GetUnanchoring():
    372             self.SetUnanchoring(None)
    373 
    374     def IsUnanchoring(self):
    375         return self.unanchoring is not None
    376 
    377     def GetUnanchorTime(self):
    378         return self.unanchoring
    379 
    380     def HasUnanchoringExpired(self):
    381         unanchoring = self.GetUnanchoring()
    382         return unanchoring is not None and unanchoring <= self.GetTimeNow()
    383 
    384     def IsShieldDamaged(self):
    385         ALMOST_MAX_SHIELD_LEVEL = 0.995
    386         return self.GetShield() < ALMOST_MAX_SHIELD_LEVEL
    387 
    388     def IsArmorDamaged(self):
    389         return self.GetArmor() < 1
    390 
    391     def IsHullDamaged(self):
    392         return self.GetHull() < 1
    393 
    394     def IsDamaged(self):
    395         if self.GetDamage() == (None, None, None):
    396             return False
    397         return self.IsShieldDamaged() or self.IsArmorDamaged() or self.IsHullDamaged()
    398 
    399     def IsShieldOrArmorFullyHealed(self):
    400         if self.GetDamage() == (None, None, None):
    401             return True
    402         if not self.IsShieldDamaged():
    403             return True
    404         if not self.IsArmorDamaged():
    405             return True
    406         return False
    407 
    408     def IsInFittingState(self):
    409         return self.GetState() == structures.STATE_FITTING_INVULNERABLE
    410 
    411     def SetDamage(self, shield, armor, hull):
    412         damage = (max(min(float(shield), 1.0), 0.0), max(min(float(armor), 1.0), 0.0), max(min(float(hull), 1.0), 0.0))
    413         if damage != self.GetDamage():
    414             self.damage = damage
    415             self.SaveDamage(damage)
    416             self.HandleDamageChanged()
    417             self.OnDamageChanged(self, damage)
    418 
    419     def GetDamage(self):
    420         return self.damage
    421 
    422     def CanHaveAutoRepair(self):
    423         return True
    424 
    425     def ShouldRepair(self):
    426         if self.state in structures.STATES_THAT_REQUIRE_AUTO_REPAIR_TO_REPAIR and not self.CanHaveAutoRepair():
    427             return False
    428         elif not self.IsVulnerable():
    429             return False
    430         elif self.state == structures.STATE_DEPLOY_VULNERABLE:
    431             return self.IsHullDamaged()
    432         elif self.state == structures.STATE_ONLINING_VULNERABLE:
    433             return self.IsDamaged() and self.HasDeedInSlot()
    434         else:
    435             return self.IsDamaged()
    436 
    437     def ShouldRepairPause(self):
    438         return self.GetLastAggressed()
    439 
    440     def IsRepairing(self):
    441         return bool(self.repairing)
    442 
    443     def GetRepairing(self):
    444         return self.repairing
    445 
    446     def SetRepairing(self, repairing):
    447         if repairing is not None and not isinstance(repairing, bool):
    448             raise TypeError('Repairing must be a bool or None')
    449         if repairing != self.repairing:
    450             self.repairing = repairing
    451             self.SaveRepairing(repairing)
    452             self.OnRepairingChanged(self, repairing)
    453 
    454     def IsAggressed(self):
    455         if self.state == structures.STATE_SHIELD_VULNERABLE:
    456             return self.IsShieldDamaged()
    457         if self.state == structures.STATE_ARMOR_VULNERABLE:
    458             return self.IsArmorDamaged()
    459         if self.state == structures.STATE_HULL_VULNERABLE:
    460             return self.IsHullDamaged()
    461         if self.state == structures.STATE_ANCHOR_VULNERABLE:
    462             return self.IsHullDamaged()
    463         if self.state == structures.STATE_DEPLOY_VULNERABLE:
    464             return self.IsHullDamaged()
    465 
    466     def IsDamageBelowNotificationThreshold(self):
    467         if self.state == structures.STATE_SHIELD_VULNERABLE:
    468             return self.GetShield() < 0.95
    469         return True
    470 
    471     def GetFirstAggressed(self):
    472         return self.firstAggressed
    473 
    474     def GetLastAggressed(self):
    475         return self.lastAggressed
    476 
    477     def SetAggression(self, aggressed, latest = None):
    478         if aggressed is not None and not isinstance(aggressed, datetime.datetime):
    479             raise TypeError('Aggression time must be a datetime object or None')
    480         if latest is not None and not isinstance(aggressed, datetime.datetime):
    481             raise TypeError('Latest aggression time must be a datetime object or None')
    482         firstAggressed = self.GetFirstAggressed()
    483         if aggressed != firstAggressed or latest != self.GetLastAggressed():
    484             self.firstAggressed = aggressed
    485             self.lastAggressed = latest
    486             self.OnAggressionChanged(self, self.firstAggressed, self.lastAggressed)
    487         if self.firstAggressed is None:
    488             self.notificationSent = False
    489         if self.firstAggressed and not self.notificationSent:
    490             if self.IsDamageBelowNotificationThreshold():
    491                 self.notificationSent = True
    492                 self.OnFirstAggressed(self, self.firstAggressed)
    493 
    494     def GetShield(self):
    495         return self.GetDamage()[0]
    496 
    497     def GetArmor(self):
    498         return self.GetDamage()[1]
    499 
    500     def GetHull(self):
    501         return self.GetDamage()[2]
    502 
    503     def GetVulnerable(self):
    504         return self.vulnerable
    505 
    506     def IsVulnerable(self):
    507         return bool(self.GetVulnerable())
    508 
    509     def SetVulnerable(self, vulnerable):
    510         vulnerable = bool(int(vulnerable))
    511         existing = self.GetVulnerable()
    512         if vulnerable != existing:
    513             self.vulnerable = vulnerable
    514             self.SaveVulnerable(vulnerable)
    515             self.OnVulnerabilityChanged(self, existing, vulnerable)
    516 
    517     def SetReinforceTimes(self, weekday, hour, userID, characterID):
    518         self.reinforceTiming.SetReinforceTimes(weekday, hour)
    519         self.SaveReinforceTimes()
    520         self.OnReinforceTimeChanged(userID, characterID)
    521 
    522     def SetNextReinforceTimes(self, weekday, hour, delaySeconds, userID, characterID):
    523         applyAtTime = self.GetTimeNow() + datetime.timedelta(seconds=int(delaySeconds))
    524         self.reinforceTiming.SetNextReinforceTimes(weekday, hour, applyAtTime)
    525         self.SaveReinforceTimes()
    526         self.OnReinforceTimeChanged(userID, characterID)
    527 
    528     def GetTimeNow(self):
    529         now = datetime.datetime.utcnow()
    530         return now.replace(microsecond=0)
    531 
    532     def IsStateHullVulnerable(self):
    533         return self.state in (structures.STATE_HULL_VULNERABLE, structures.STATE_ANCHOR_VULNERABLE, structures.STATE_ONLINING_VULNERABLE)
    534 
    535     def IsStateArmorVulnerable(self):
    536         return self.state == structures.STATE_ARMOR_VULNERABLE
    537 
    538     def IsStateShieldVulnerable(self):
    539         return self.state == structures.STATE_SHIELD_VULNERABLE
    540 
    541     def FullyRepairSelf(self):
    542         self.SetDamage(1.0, 1.0, 1.0)
    543 
    544     def RepairHullOnly(self):
    545         self.SetDamage(0.0, 0.0, 1.0)
    546 
    547     def HasOnlineServiceModule(self):
    548         raise NotImplementedError()
    549 
    550     def SetAbandonAt(self, abandonAt):
    551         if abandonAt is not None:
    552             if not isinstance(abandonAt, datetime.datetime):
    553                 raise TypeError('abandonAt must be a datetime object or None')
    554             if self.upkeepState != structures.UPKEEP_STATE_LOW_POWER:
    555                 raise RuntimeError('Structure must be in Low Power to set abandon timer - current upkeep state = %s' % self.upkeepState)
    556             if not self.StructureAllowsAbandonment():
    557                 raise RuntimeError('This structure does not allow abandonment')
    558         self.abandonAt = abandonAt
    559         self.SaveAbandoned()
    560 
    561     def UpdateUpkeep(self):
    562         if self.upkeepState == structures.UPKEEP_STATE_FULL_POWER:
    563             if not self.HasOnlineServiceModule():
    564                 self.SetUpkeepState(structures.UPKEEP_STATE_LOW_POWER)
    565             elif self.abandonAt is not None:
    566                 self.SetAbandonAt(None)
    567         elif self.upkeepState == structures.UPKEEP_STATE_LOW_POWER:
    568             if self.HasOnlineServiceModule():
    569                 self.SetUpkeepState(structures.UPKEEP_STATE_FULL_POWER)
    570             elif self.abandonAt is None:
    571                 if self.StructureAllowsAbandonment():
    572                     abandonDurationSec = randint(structures.ABANDONING_TIME_MIN, structures.ABANDONING_TIME_MAX)
    573                     self.SetAbandonAt(self.GetTimeNow() + datetime.timedelta(seconds=abandonDurationSec))
    574             elif self.abandonAt < self.GetTimeNow():
    575                 self.SetUpkeepState(structures.UPKEEP_STATE_ABANDONED)
    576             else:
    577                 self.ConsiderSendingImpendingAbandonWarnings()
    578         elif self.upkeepState == structures.UPKEEP_STATE_ABANDONED:
    579             if self.HasOnlineServiceModule():
    580                 self.SetUpkeepState(structures.UPKEEP_STATE_FULL_POWER)
    581         else:
    582             logger.warn('UpdateUpkeep called for for unrecognised upkeep state - falling back to Full Power [%s]', self)
    583             self.SetUpkeepState(structures.UPKEEP_STATE_FULL_POWER)
    584 
    585     def SetUpkeepState(self, newUpkeepState):
    586         if newUpkeepState not in structures.ALL_UPKEEP_STATES:
    587             raise ValueError('newUpkeepState must be one of ALL_UPKEEP_STATES')
    588         if self.upkeepState != newUpkeepState:
    589             oldUpkeepState = self.upkeepState
    590             self.upkeepState = newUpkeepState
    591             self.SaveUpkeepState()
    592             self.OnUpkeepStateChanged(oldUpkeepState, newUpkeepState)
    593             self.UpdateUpkeep()
    594 
    595     def OnUpkeepStateChanged(self, oldUpkeepState, newUpkeepState):
    596         pass
    597 
    598     def IsUpkeepState_Full(self):
    599         return self.upkeepState == structures.UPKEEP_STATE_FULL_POWER
    600 
    601     def IsUpkeepState_Low(self):
    602         return self.upkeepState == structures.UPKEEP_STATE_LOW_POWER
    603 
    604     def IsUpkeepState_Abandoned(self):
    605         return self.upkeepState == structures.UPKEEP_STATE_ABANDONED
    606 
    607     def CanApplyNextReinforceTime(self):
    608         return self.state == structures.STATE_SHIELD_VULNERABLE and not self.IsDamaged()
    609 
    610     def CanActivateEquipment(self):
    611         return self.state in structures.CAN_ACTIVATE_EQUIPMENT_IN_STATE
    612 
    613     def PlaySpecialFX(self, guid):
    614         pass
    615 
    616     def ConsiderSendingImpendingAbandonWarnings(self):
    617         raise NotImplementedError()
    618 
    619     def StructureAllowsAbandonment(self):
    620         return True
    621 
    622     def UpdateDeed(self):
    623         newDeedState = self.CalculateDeedState()
    624         if newDeedState == self.deedState:
    625             return
    626         self.deedState = newDeedState
    627         self.Update()
    628         self.OnDeedStateChanged()
    629 
    630     def HasDeedInSlot(self):
    631         return self.deedState
    632 
    633     def CalculateDeedState(self):
    634         raise NotImplementedError()
    635 
    636     def OnDeedStateChanged(self):
    637         pass
    638 
    639     def IsInsurgencyFOB(self):
    640         return False
    641 
    642 
    643 class CountdownTimer(object):
    644 
    645     def __init__(self, structure):
    646         self._structure = structure
    647         self.endDateTime = None
    648         self.startDateTime = None
    649         self.pausedDateTime = None
    650         self.OnTimerChanged = signals.Signal(signalName='OnTimerChanged')
    651 
    652     def LoadFromRow(self, structureRow):
    653         from carbon.common.script.util.format import BlueToDate
    654         self.startDateTime = BlueToDate(structureRow.timerStart)
    655         self.endDateTime = BlueToDate(structureRow.timerEnd)
    656 
    657     def IsRunning(self):
    658         return self.startDateTime and self.endDateTime
    659 
    660     def GetTimerRemaining(self):
    661         if self.endDateTime is not None:
    662             return int((self.endDateTime - self._structure.GetTimeNow()).total_seconds())
    663 
    664     def IsExpired(self):
    665         return self.endDateTime is None or self.endDateTime <= self._structure.GetTimeNow()
    666 
    667     def ClearTimer(self):
    668         if self.endDateTime is not None or self.startDateTime is not None or self.pausedDateTime is not None:
    669             self.startDateTime = None
    670             self.endDateTime = None
    671             self.pausedDateTime = None
    672             self._structure.SaveTimer(self.startDateTime, self.endDateTime, self.pausedDateTime)
    673             self.OnTimerChanged(self, self.startDateTime, self.endDateTime, self.pausedDateTime)
    674 
    675     def SetTimer(self, endDateTime, startDateTime = None, pausedDateTime = None):
    676         if endDateTime is not None and not isinstance(endDateTime, datetime.datetime):
    677             raise TypeError('Timer must be a datetime object or None')
    678         if isinstance(pausedDateTime, bool):
    679             pausedDateTime = self._structure.GetTimeNow() if pausedDateTime else None
    680         elif pausedDateTime is not None and not isinstance(pausedDateTime, datetime.datetime):
    681             raise TypeError('Paused must be a datetime object, a bool or None')
    682         if endDateTime != self.endDateTime or startDateTime != self.startDateTime or pausedDateTime != self.pausedDateTime:
    683             self.startDateTime = startDateTime or self._structure.GetTimeNow()
    684             self.endDateTime = endDateTime
    685             self.pausedDateTime = pausedDateTime
    686             self._structure.SaveTimer(self.startDateTime, self.endDateTime, self.pausedDateTime)
    687             self.OnTimerChanged(self, self.startDateTime, self.endDateTime, self.pausedDateTime)
    688 
    689     def IsPaused(self):
    690         return self.pausedDateTime is not None
    691 
    692     def Pause(self):
    693         if not self.IsRunning():
    694             return
    695         if not self.pausedDateTime:
    696             self.SetTimer(self.endDateTime, self.startDateTime, self._structure.GetTimeNow())
    697 
    698     def Unpause(self):
    699         if not self.IsRunning():
    700             logger.warn('CountdownTimer.Unpause() called for structure timer that is not running. %s', self._structure)
    701             return
    702         if self.pausedDateTime:
    703             elapsed = self._structure.GetTimeNow() - self.pausedDateTime
    704             start = elapsed + self.startDateTime
    705             end = elapsed + self.endDateTime
    706             self.SetTimer(end, start)
    707 
    708     def GetTimesBlue(self):
    709         if self.startDateTime and self.endDateTime:
    710             from carbon.common.script.util.format import DateToBlue
    711             return (DateToBlue(self.startDateTime), DateToBlue(self.endDateTime), DateToBlue(self.pausedDateTime))
    712 
    713     def GetEndTimeBlue(self):
    714         from carbon.common.script.util.format import DateToBlue
    715         return DateToBlue(self.endDateTime)
    716 
    717 
    718 def LookupState(state):
    719     if state:
    720         state = str(state).lower()
    721         for value, name in structures.STATES.iteritems():
    722             if str(value) == state:
    723                 return value
    724             if name.startswith(state):
    725                 return value