Advertisement
deutscher_Adler

Untitled

Sep 15th, 2015
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.92 KB | None | 0 0
  1. # Embedded file name: EdgeDetect.py
  2. __application__ = 'Edge Detect Mod'
  3. __appShortName__ = 'EdgeDetect'
  4. __authors__ = ['GPCracker']
  5. __version__ = '0.0.5'
  6. __clientVersion__ = '0.9.7'
  7. __status__ = 'Beta'
  8. if __name__ == '__main__':
  9. modInfo = '[{}] {} v{} {} by {} (WOT Client {}).'.format(__appShortName__, __application__, __version__, __status__, ', '.join(__authors__), __clientVersion__)
  10. print modInfo
  11. from time import sleep
  12. sleep(len(modInfo) * (4.0 / 70))
  13. exit()
  14. import BigWorld
  15. import ResMgr
  16. import Keys
  17. import Math
  18.  
  19. def getClientVersion():
  20. from Account import _readClientServerVersion
  21. return _readClientServerVersion()[1].split('_')[1]
  22.  
  23.  
  24. def isCommonTest():
  25. from Account import _readClientServerVersion
  26. return _readClientServerVersion()[1].split('_')[0] == 'ct'
  27.  
  28.  
  29. def macrosSubstitution(message, **kwargs):
  30. import re
  31. for macros, value in kwargs.items():
  32. message = re.compile('\\{\\{' + macros + '\\}\\}').sub(str(value), message)
  33.  
  34. return message
  35.  
  36.  
  37. class Callback(object):
  38.  
  39. def __init__(self, time, function, *args, **kwargs):
  40. import functools, weakref
  41. proxiesL = []
  42. for arg in args:
  43. try:
  44. proxiesL.append(weakref.proxy(arg))
  45. except TypeError:
  46. proxiesL.append(arg)
  47.  
  48. proxiesD = {}
  49. for key, value in kwargs.iteritems():
  50. try:
  51. proxiesD[key] = weakref.proxy(value)
  52. except TypeError:
  53. proxiesD[key] = value
  54.  
  55. self.__cbID = BigWorld.callback(time, functools.partial(weakref.proxy(function), *proxiesL, **proxiesD))
  56. return
  57.  
  58. def __del__(self):
  59. try:
  60. BigWorld.cancelCallback(self.__cbID)
  61. except ValueError:
  62. pass
  63.  
  64. return None
  65.  
  66.  
  67. class Event(object):
  68.  
  69. def __init__(self):
  70. self.__delegates = []
  71. return None
  72.  
  73. def __iadd__(self, delegate):
  74. if delegate not in self.__delegates:
  75. self.__delegates.append(delegate)
  76. return self
  77.  
  78. def __isub__(self, delegate):
  79. if delegate in self.__delegates:
  80. self.__delegates.remove(delegate)
  81. return self
  82.  
  83. def __call__(self, *args, **kwargs):
  84. for delegate in self.__delegates:
  85. try:
  86. delegate(*args, **kwargs)
  87. except:
  88. import traceback
  89. traceback.print_exc()
  90.  
  91. return
  92.  
  93. def __repr__(self):
  94. return 'Event({}):{}'.format(len(self.__delegates), repr(self.__delegates))
  95.  
  96.  
  97. class Trigger(object):
  98.  
  99. def __init__(self, status = False, onActivate = None, onDeactivate = None, onChange = None):
  100. self.__status = status
  101. self.onActivate = onActivate if onActivate is not None else Event()
  102. self.onDeactivate = onDeactivate if onDeactivate is not None else Event()
  103. self.onChange = onChange if onChange is not None else Event()
  104. return
  105.  
  106. @property
  107. def status(self):
  108. return self.__status
  109.  
  110. @status.setter
  111. def status(self, value):
  112. if value and not self.__status:
  113. self.onActivate()
  114. self.onChange(True)
  115. elif not value and self.__status:
  116. self.onDeactivate()
  117. self.onChange(False)
  118. self.__status = bool(value)
  119. return None
  120.  
  121. def enable(self):
  122. self.status = True
  123. return None
  124.  
  125. def disable(self):
  126. self.status = False
  127. return None
  128.  
  129. def __repr__(self):
  130. return 'Trigger(onActivate = {}, onDeactivate = {}, onChange = {})'.format(repr(self.onActivate), repr(self.onDeactivate), repr(self.onChange))
  131.  
  132.  
  133. class DelayTrigger(Trigger):
  134.  
  135. def __init__(self, timestampGetter, dynamicStatus = False, status = False, activateDelay = 0.0, deactivateDelay = 0.0, onActivate = None, onDeactivate = None, onChange = None):
  136. self.__timestampGetter = timestampGetter
  137. self.__dynamicStatus = dynamicStatus
  138. self.__dynamicStatusTimestamp = None
  139. self.__activateDelay = activateDelay
  140. self.__deactivateDelay = deactivateDelay
  141. return super(DelayTrigger, self).__init__(status, onActivate, onDeactivate, onChange)
  142.  
  143. def __repr__(self):
  144. return 'DelayTrigger(onActivate = {}, onDeactivate = {}, onChange = {})'.format(repr(self.onActivate), repr(self.onDeactivate), repr(self.onChange))
  145.  
  146. @property
  147. def dynamicStatus(self):
  148. return self.__dynamicStatus
  149.  
  150. @dynamicStatus.setter
  151. def dynamicStatus(self, value):
  152. delay = self.__activateDelay if self.__dynamicStatus else self.__deactivateDelay
  153. if value != self.__dynamicStatus:
  154. self.__dynamicStatus = value
  155. self.__dynamicStatusTimestamp = self.__timestampGetter()
  156. elif self.__dynamicStatusTimestamp + delay < self.__timestampGetter():
  157. self.status = self.__dynamicStatus
  158. return None
  159.  
  160. def dynamicEnable(self):
  161. self.dynamicStatus = True
  162. return None
  163.  
  164. def dynamicDisable(self):
  165. self.dynamicStatus = False
  166. return None
  167.  
  168.  
  169. class ThreeStateTrigger(object):
  170.  
  171. def __init__(self, status = None, onActivate = None, onDeactivate = None, onReset = None, onChange = None):
  172. self.__status = status
  173. self.onActivate = onActivate if onActivate is not None else Event()
  174. self.onDeactivate = onDeactivate if onDeactivate is not None else Event()
  175. self.onReset = onReset if onReset is not None else Event()
  176. self.onChange = onChange if onChange is not None else Event()
  177. return
  178.  
  179. @property
  180. def status(self):
  181. return self.__status
  182.  
  183. @status.setter
  184. def status(self, value):
  185. if value != self.__status:
  186. if value == True:
  187. self.onActivate()
  188. elif value == False:
  189. self.onDeactivate()
  190. elif value == None:
  191. self.onReset()
  192. else:
  193. raise TypeError()
  194. self.onChange(value)
  195. self.__status = value
  196. return
  197.  
  198. def enable(self):
  199. self.status = True
  200. return None
  201.  
  202. def disable(self):
  203. self.status = False
  204. return None
  205.  
  206. def reset(self):
  207. self.status = None
  208. return
  209.  
  210. def __repr__(self):
  211. return 'ThreeStateTrigger(onActivate = {}, onDeactivate = {}, onReset = {}, onChange = {}))'.format(repr(self.onActivate), repr(self.onDeactivate), repr(self.onReset), repr(self.onChange))
  212.  
  213.  
  214. class ConfigReader(object):
  215. BASE_TYPES = ('Binary', 'Blob', 'Bool', 'Float', 'Int', 'Int64', 'Matrix', 'String', 'Vector2', 'Vector3', 'Vector4', 'WideString')
  216.  
  217. def readBase(self, typeName, xml, default):
  218. if xml is not None:
  219. return getattr(xml, 'as' + typeName)
  220. else:
  221. return default
  222.  
  223. def readDict(self, xml, default):
  224. getSectionXML = lambda sectionName: (xml[sectionName] if xml is not None else None)
  225. return dict([ (sectionName, self.readSection(getSectionXML(sectionName), default[sectionName])) for sectionName in default.keys() ])
  226.  
  227. def readExt(self, typeName, xml, default):
  228. raise typeName in self.__extTypes or AssertionError('No reader for {} type'.format(typeName))
  229. return self.__extTypes[typeName](xml, default)
  230.  
  231. def readList(self, xml, default, itemName = 'item', itemType = 'String', itemDefault = ''):
  232. if xml is not None:
  233. return [ self.readSection(xmlSection, (itemType, itemDefault)) for sectionName, xmlSection in xml.items() if sectionName == itemName ]
  234. else:
  235. return [ self.readSection(None, (itemType, defaultItem)) for defaultItem in default ]
  236.  
  237. def readCustomDict(self, xml, default, itemType = 'String', itemDefault = ''):
  238. if xml is not None:
  239. return dict([ (sectionName, self.readSection(xmlSection, (itemType, itemDefault))) for sectionName, xmlSection in xml.items() ])
  240. else:
  241. return dict([ (sectionName, self.readSection(None, (itemType, defaultItem))) for sectionName, defaultItem in default.items() ])
  242.  
  243. def readSection(self, xmlSection, defSection):
  244. if isinstance(defSection, (list, tuple)):
  245. sectionType, sectionDefault = defSection
  246. if sectionType in self.BASE_TYPES:
  247. return self.readBase(sectionType, xmlSection, sectionDefault)
  248. else:
  249. return self.readExt(sectionType, xmlSection, sectionDefault)
  250. else:
  251. if isinstance(defSection, dict):
  252. return self.readDict(xmlSection, defSection)
  253. raise IOError('Invalid config parameter type "{}".'.format(type(defSection).__name__))
  254. return None
  255.  
  256. def readConfig(self, xmlPath, defConfig):
  257. return self.readSection(ResMgr.openSection(xmlPath), defConfig)
  258.  
  259. def __init__(self, extTypes = {}):
  260. self.__extTypes = extTypes
  261. return None
  262.  
  263. @property
  264. def extTypes(self):
  265. return self.__extTypes
  266.  
  267. @extTypes.setter
  268. def extTypes(self, value):
  269. self.__extTypes = value
  270. return None
  271.  
  272. def __del__(self):
  273. return None
  274.  
  275.  
  276. _config_ = None
  277.  
  278. def defaultConfig():
  279. return {'modEnabled': ('Bool', True),
  280. 'ignoreClientVersion': ('Bool', False),
  281. 'hookSetTimeout': ('Float', 3.0),
  282. 'modLoadedMessage': ('WideString', u'{} loaded.'.format(__application__)),
  283. 'modUpdateMessage': ('WideString', u'Please update {}!'.format(__application__)),
  284. 'edgeHighlight': {'color0': ('Vector4', Math.Vector4(127, 127, 127, 255)),
  285. 'color1': ('Vector4', Math.Vector4(255, 18, 7, 255)),
  286. 'color2': ('Vector4', Math.Vector4(124, 214, 6, 255)),
  287. 'color3': ('Vector4', Math.Vector4(255, 255, 255, 255)),
  288. 'selfColorIndex': ('Int', 0),
  289. 'selfAllocateInvisiblePartsOnly': ('Bool', True),
  290. 'enemyColorIndex': ('Int', -1),
  291. 'enemyAllocateInvisiblePartsOnly': ('Bool', False),
  292. 'targetEnemyColorIndex': ('Int', 1),
  293. 'targetEnemyAllocateInvisiblePartsOnly': ('Bool', False),
  294. 'autoAimEnemyColorIndex': ('Int', -1),
  295. 'autoAimEnemyAllocateInvisiblePartsOnly': ('Bool', False),
  296. 'friendColorIndex': ('Int', -1),
  297. 'friendAllocateInvisiblePartsOnly': ('Bool', False),
  298. 'targetFriendColorIndex': ('Int', 2),
  299. 'targetFriendAllocateInvisiblePartsOnly': ('Bool', False)}}
  300.  
  301.  
  302. def readConfig():
  303. mainSection = ResMgr.openSection(__file__.replace('.pyc', '.xml'))
  304. if mainSection is None:
  305. print '[{}] Config loading failed.'.format(__appShortName__)
  306. else:
  307. print '[{}] Config successfully loaded.'.format(__appShortName__)
  308. return ConfigReader().readSection(mainSection, defaultConfig())
  309.  
  310.  
  311. def new_PlayerAvatar_onEnterWorld(self, *args, **kwargs):
  312. result = old_PlayerAvatar_onEnterWorld(self, *args, **kwargs)
  313. from helpers import EdgeDetectColorController
  314. EdgeDetectColorController.g_instance.updateColors()
  315. return result
  316.  
  317.  
  318. def new_PlayerAvatar_targetFocus(self, entity):
  319. global _config_
  320. result = old_PlayerAvatar_targetFocus(self, entity)
  321. import constants
  322. import Vehicle
  323. isGuiVisible = self._PlayerAvatar__isGuiVisible
  324. isInTutorial = self.arena is not None and self.arena.guiType == constants.ARENA_GUI_TYPE.TUTORIAL
  325. if (isGuiVisible or isInTutorial) and isinstance(entity, Vehicle.Vehicle) and entity.isStarted and entity.isAlive():
  326. BigWorld.wgDelEdgeDetectEntity(entity)
  327. if entity is self.autoAimVehicle:
  328. colorIndex = _config_['edgeHighlight']['autoAimEnemyColorIndex']
  329. allocateInvisiblePartsOnly = _config_['edgeHighlight']['autoAimEnemyAllocateInvisiblePartsOnly']
  330. elif self.team == entity.publicInfo['team']:
  331. colorIndex = _config_['edgeHighlight']['targetFriendColorIndex']
  332. allocateInvisiblePartsOnly = _config_['edgeHighlight']['targetFriendAllocateInvisiblePartsOnly']
  333. else:
  334. colorIndex = _config_['edgeHighlight']['targetEnemyColorIndex']
  335. allocateInvisiblePartsOnly = _config_['edgeHighlight']['targetEnemyAllocateInvisiblePartsOnly']
  336. if colorIndex >= 0:
  337. BigWorld.wgAddEdgeDetectEntity(entity, colorIndex, allocateInvisiblePartsOnly)
  338. return result
  339.  
  340.  
  341. def new_PlayerAvatar_targetBlur(self, prevEntity):
  342. result = old_PlayerAvatar_targetBlur(self, prevEntity)
  343. import constants
  344. import Vehicle
  345. isGuiVisible = self._PlayerAvatar__isGuiVisible
  346. isInTutorial = self.arena is not None and self.arena.guiType == constants.ARENA_GUI_TYPE.TUTORIAL
  347. if (isGuiVisible or isInTutorial) and isinstance(prevEntity, Vehicle.Vehicle) and prevEntity.isStarted and prevEntity.isAlive():
  348. BigWorld.wgDelEdgeDetectEntity(prevEntity)
  349. if prevEntity is self.autoAimVehicle:
  350. colorIndex = _config_['edgeHighlight']['autoAimEnemyColorIndex']
  351. allocateInvisiblePartsOnly = _config_['edgeHighlight']['autoAimEnemyAllocateInvisiblePartsOnly']
  352. elif self.team == prevEntity.publicInfo['team']:
  353. colorIndex = _config_['edgeHighlight']['friendColorIndex']
  354. allocateInvisiblePartsOnly = _config_['edgeHighlight']['friendAllocateInvisiblePartsOnly']
  355. else:
  356. colorIndex = _config_['edgeHighlight']['enemyColorIndex']
  357. allocateInvisiblePartsOnly = _config_['edgeHighlight']['enemyAllocateInvisiblePartsOnly']
  358. if colorIndex >= 0:
  359. BigWorld.wgAddEdgeDetectEntity(prevEntity, colorIndex, allocateInvisiblePartsOnly)
  360. return result
  361.  
  362.  
  363. def new_PlayerAvatar_setVisibleGUI(self, bool):
  364. result = old_PlayerAvatar_setVisibleGUI(self, bool)
  365. import Vehicle
  366. playerVehicle = BigWorld.entity(self.playerVehicleID)
  367. if isinstance(playerVehicle, Vehicle.Vehicle) and playerVehicle.isStarted and playerVehicle.isAlive() and bool:
  368. BigWorld.wgDelEdgeDetectEntity(playerVehicle)
  369. colorIndex = _config_['edgeHighlight']['selfColorIndex']
  370. allocateInvisiblePartsOnly = _config_['edgeHighlight']['selfAllocateInvisiblePartsOnly']
  371. BigWorld.wgAddEdgeDetectEntity(playerVehicle, colorIndex, allocateInvisiblePartsOnly)
  372. return result
  373.  
  374.  
  375. def new_PlayerAvatar_autoAimVehID_getter(self):
  376. if hasattr(self, '__autoAimVehID'):
  377. return self.__autoAimVehID
  378. return 0
  379.  
  380.  
  381. def new_PlayerAvatar_autoAimVehID_setter(self, value):
  382. import Vehicle
  383. oldAutoAimVehicle = BigWorld.entity(self.__autoAimVehID) if hasattr(self, '__autoAimVehID') else None
  384. self.__autoAimVehID = value
  385. newAutoAimVehicle = BigWorld.entity(self.__autoAimVehID)
  386. if isinstance(oldAutoAimVehicle, Vehicle.Vehicle) and oldAutoAimVehicle.isStarted and oldAutoAimVehicle.isAlive():
  387. BigWorld.wgDelEdgeDetectEntity(oldAutoAimVehicle)
  388. if oldAutoAimVehicle is BigWorld.target():
  389. if oldAutoAimVehicle.publicInfo['team'] == BigWorld.player().team:
  390. colorIndex = _config_['edgeHighlight']['targetFriendColorIndex']
  391. allocateInvisiblePartsOnly = _config_['edgeHighlight']['targetFriendAllocateInvisiblePartsOnly']
  392. else:
  393. colorIndex = _config_['edgeHighlight']['targetEnemyColorIndex']
  394. allocateInvisiblePartsOnly = _config_['edgeHighlight']['targetEnemyAllocateInvisiblePartsOnly']
  395. elif oldAutoAimVehicle.publicInfo['team'] == BigWorld.player().team:
  396. colorIndex = _config_['edgeHighlight']['friendColorIndex']
  397. allocateInvisiblePartsOnly = _config_['edgeHighlight']['friendAllocateInvisiblePartsOnly']
  398. else:
  399. colorIndex = _config_['edgeHighlight']['enemyColorIndex']
  400. allocateInvisiblePartsOnly = _config_['edgeHighlight']['enemyAllocateInvisiblePartsOnly']
  401. if colorIndex >= 0:
  402. BigWorld.wgAddEdgeDetectEntity(oldAutoAimVehicle, colorIndex, allocateInvisiblePartsOnly)
  403. if isinstance(newAutoAimVehicle, Vehicle.Vehicle) and newAutoAimVehicle.isStarted and newAutoAimVehicle.isAlive():
  404. BigWorld.wgDelEdgeDetectEntity(newAutoAimVehicle)
  405. colorIndex = _config_['edgeHighlight']['autoAimEnemyColorIndex']
  406. allocateInvisiblePartsOnly = _config_['edgeHighlight']['autoAimEnemyAllocateInvisiblePartsOnly']
  407. if colorIndex >= 0:
  408. BigWorld.wgAddEdgeDetectEntity(newAutoAimVehicle, colorIndex, allocateInvisiblePartsOnly)
  409. return
  410.  
  411.  
  412. def new_Vehicle_startVisual(self):
  413. result = old_Vehicle_startVisual(self)
  414. if self.isStarted and self.isAlive():
  415. BigWorld.wgDelEdgeDetectEntity(self)
  416. if self.isPlayer:
  417. colorIndex = _config_['edgeHighlight']['selfColorIndex']
  418. allocateInvisiblePartsOnly = _config_['edgeHighlight']['selfAllocateInvisiblePartsOnly']
  419. elif BigWorld.player().team == self.publicInfo['team']:
  420. colorIndex = _config_['edgeHighlight']['friendColorIndex']
  421. allocateInvisiblePartsOnly = _config_['edgeHighlight']['friendAllocateInvisiblePartsOnly']
  422. else:
  423. colorIndex = _config_['edgeHighlight']['enemyColorIndex']
  424. allocateInvisiblePartsOnly = _config_['edgeHighlight']['enemyAllocateInvisiblePartsOnly']
  425. if colorIndex >= 0:
  426. BigWorld.wgAddEdgeDetectEntity(self, colorIndex, allocateInvisiblePartsOnly)
  427. return result
  428.  
  429.  
  430. def new_Vehicle_stopVisual(self):
  431. if self.isStarted and self.isAlive():
  432. BigWorld.wgDelEdgeDetectEntity(self)
  433. return old_Vehicle_stopVisual(self)
  434.  
  435.  
  436. def new_Vehicle_onVehicleDeath(self, *args, **kwargs):
  437. result = old_Vehicle_onVehicleDeath(self, *args, **kwargs)
  438. if self.isStarted:
  439. BigWorld.wgDelEdgeDetectEntity(self)
  440. return result
  441.  
  442.  
  443. def new_EdgeDetectColorController_changeColor(self, diff):
  444. from Account import PlayerAccount
  445. if isinstance(BigWorld.player(), PlayerAccount):
  446. return old_EdgeDetectColorController_changeColor(self, diff)
  447. color0 = _config_['edgeHighlight']['color0'].scale(1.0 / 255.0)
  448. color1 = _config_['edgeHighlight']['color1'].scale(1.0 / 255.0)
  449. color2 = _config_['edgeHighlight']['color2'].scale(1.0 / 255.0)
  450. color3 = _config_['edgeHighlight']['color3'].scale(1.0 / 255.0)
  451. BigWorld.wgSetEdgeDetectColors((color0,
  452. color1,
  453. color2,
  454. color3))
  455.  
  456.  
  457. def injectHooks():
  458. global old_PlayerAvatar_setVisibleGUI
  459. global old_Vehicle_stopVisual
  460. global old_EdgeDetectColorController_changeColor
  461. global old_PlayerAvatar_onEnterWorld
  462. global old_PlayerAvatar_targetFocus
  463. global old_PlayerAvatar_targetBlur
  464. global old_Vehicle_startVisual
  465. global old_Vehicle_onVehicleDeath
  466. from Avatar import PlayerAvatar
  467. old_PlayerAvatar_onEnterWorld = PlayerAvatar.onEnterWorld
  468. PlayerAvatar.onEnterWorld = new_PlayerAvatar_onEnterWorld
  469. old_PlayerAvatar_targetFocus = PlayerAvatar.targetFocus
  470. PlayerAvatar.targetFocus = new_PlayerAvatar_targetFocus
  471. old_PlayerAvatar_targetBlur = PlayerAvatar.targetBlur
  472. PlayerAvatar.targetBlur = new_PlayerAvatar_targetBlur
  473. old_PlayerAvatar_setVisibleGUI = PlayerAvatar._PlayerAvatar__setVisibleGUI
  474. PlayerAvatar._PlayerAvatar__setVisibleGUI = new_PlayerAvatar_setVisibleGUI
  475. PlayerAvatar._PlayerAvatar__autoAimVehID = property(new_PlayerAvatar_autoAimVehID_getter, new_PlayerAvatar_autoAimVehID_setter)
  476. from Vehicle import Vehicle
  477. old_Vehicle_startVisual = Vehicle.startVisual
  478. Vehicle.startVisual = new_Vehicle_startVisual
  479. old_Vehicle_stopVisual = Vehicle.stopVisual
  480. Vehicle.stopVisual = new_Vehicle_stopVisual
  481. old_Vehicle_onVehicleDeath = Vehicle._Vehicle__onVehicleDeath
  482. Vehicle._Vehicle__onVehicleDeath = new_Vehicle_onVehicleDeath
  483. from helpers.EdgeDetectColorController import EdgeDetectColorController
  484. old_EdgeDetectColorController_changeColor = EdgeDetectColorController._EdgeDetectColorController__changeColor
  485. EdgeDetectColorController._EdgeDetectColorController__changeColor = new_EdgeDetectColorController_changeColor
  486. return None
  487.  
  488.  
  489. def showStartupMessage(message):
  490. from gui import SystemMessages
  491. import functools
  492. if SystemMessages.g_instance is None:
  493. BigWorld.callback(1.0, functools.partial(showStartupMessage, message))
  494. elif message is not None and message != '':
  495. SystemMessages.pushMessage(message)
  496. return
  497.  
  498.  
  499. def loadMod():
  500. global _config_
  501. _config_ = readConfig()
  502. if _config_['ignoreClientVersion'] or getClientVersion() == __clientVersion__:
  503. if _config_['modEnabled']:
  504. BigWorld.callback(_config_['hookSetTimeout'], injectHooks)
  505. showStartupMessage(_config_['modLoadedMessage'])
  506. else:
  507. showStartupMessage(_config_['modUpdateMessage'])
  508. print '***** Please update {}! *****'.format(__application__)
  509. return None
  510.  
  511.  
  512. loadMod()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement