stoneharry

Untitled

May 12th, 2012
165
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 43.58 KB | None | 0 0
  1. from collections import deque
  2. from math import cos, sin, sqrt, ceil, pi
  3. from random import randrange, uniform, choice
  4. from operator import itemgetter, attrgetter
  5. from itertools import product
  6.  
  7. from twisted.internet import reactor
  8. from twisted.internet.task import LoopingCall
  9. from pyspades.server import Territory
  10. from pyspades.server import orientation_data, move_object, grenade_packet
  11. from pyspades.server import block_action, set_color, position_data
  12. from pyspades.world import Grenade
  13. from pyspades.common import Vertex3, Quaternion, make_color, coordinates
  14. from pyspades.collision import vector_collision, distance_3d_vector
  15. from pyspades.constants import *
  16. from commands import name, add, get_player, admin
  17.  
  18. ALLOW_KRAKEN_COMMAND = False
  19.  
  20. USE_DAYCYCLE = True
  21. RESPAWN_TIME = 15
  22. FALLING_BLOCK_COLOR = 0x606060
  23. FALLING_BLOCK_DAMAGE = 100
  24. FALLING_BLOCK_Z = 0
  25. REGEN_ONSET = 2.0
  26. REGEN_FREQUENCY = 0.15
  27. REGEN_AMOUNT = 3
  28. WATER_DAMAGE = 25
  29. GRAB_DAMAGE = 40
  30. EYE_PAIN_TIME = 8.0
  31. KRAKEN_BLACK = 0x000000
  32. KRAKEN_BLOOD = make_color(120, 255, 120)
  33. KRAKEN_EYE_SMALL = [
  34.     ( 0, 0, -1, 0xC00000),
  35.     ( 0, 0, -2, 0x400000),
  36.     ( 0, 0, -3, 0xC00000),
  37.     (-1, 0, -1, 0xFF0000),
  38.     (-1, 0, -2, 0xC00000),
  39.     (-1, 0, -3, 0x800000),
  40.     ( 1, 0, -1, 0xFF0000),
  41.     ( 1, 0, -2, 0xC00000),
  42.     ( 1, 0, -3, 0xFFFFFF)]
  43. KRAKEN_EYE_SMALL_CLOSED = [
  44.     (-1, 0, -1, 0x000000),
  45.     (-1, 0, -2, 0x000000),
  46.     (-1, 0, -3, 0x000000),
  47.     ( 1, 0, -1, 0x000000),
  48.     ( 1, 0, -2, 0x000000),
  49.     ( 1, 0, -3, 0x000000),
  50.     ( 0, 0, -1, 0x000000),
  51.     ( 0, 0, -2, 0x000000),
  52.     ( 0, 0, -3, 0x000000)]
  53.  
  54. def cube(s):
  55.     s0, s1 = -s / 2 + 1, s / 2 + 1
  56.     return product(xrange(s0, s1), repeat = 3)
  57.  
  58. def prism(x, y, z, w, d, h):
  59.     return product(xrange(x, x + w), xrange(y, y + d), xrange(z, z + h))
  60.  
  61. def plane(r):
  62.     r0, r1 = -r / 2 + 1, r / 2 + 1
  63.     return product(xrange(r0, r1), repeat = 2)
  64.  
  65. def disc(rr, x = 0, y = 0, min_rr = None):
  66.     for u, v in plane(rr):
  67.         d = u * u + v * v
  68.         if d > rr or (min_rr and d < min_rr):
  69.             continue
  70.         yield x + u, y + v
  71.  
  72. def sphere(r, x = 0, y = 0, z = 0, min_r = None):
  73.     rr = r * r
  74.     min_rr = min_r and min_r * min_r
  75.     for w, v, u in cube(r):
  76.         d = u * u + v * v + w * w
  77.         if d > rr or (min_r and d < min_rr):
  78.             continue
  79.         yield x + u, y + v, z + w
  80.  
  81. def aabb(x, y, z, i, j, k, w, d, h):
  82.     return not (x < i or x > i + w or y < j or y > j + d or z < k or z > k + h)
  83.  
  84. def aabb_centered(x, y, z, i, j, k, s):
  85.     return not (x < i - s or x > i + s or y < j - s or y > j + s or
  86.         z < k - s or z > k + s)
  87.  
  88. def randrangerect(x1, y1, x2, y2):
  89.     return randrange(x1, x2), randrange(y1, y2)
  90.  
  91. def fall_eta(height):
  92.     return 2.0 * (height / 64.0) ** 0.75
  93.  
  94. def is_valid_enemy(player):
  95.     return not (player.world_object is None or player.world_object.dead or
  96.         player.grabbed_by or player.trapped or player.regenerating or player.god)
  97.  
  98. class Animated:
  99.     blocks_per_cycle = 3
  100.     build_interval = 0.01
  101.     build_queue = None
  102.     build_loop = None
  103.     blocks = None
  104.    
  105.     def __init__(self, protocol):
  106.         self.protocol = protocol
  107.         self.build_queue = deque()
  108.         self.build_loop = LoopingCall(self.build_cycle)
  109.         self.build_loop.start(self.build_interval)
  110.         self.blocks = set()
  111.    
  112.     def build_cycle(self):
  113.         if not self.build_queue:
  114.             return        
  115.         blocks_left = self.blocks_per_cycle
  116.         last_color = None
  117.         while self.build_queue and blocks_left > 0:
  118.             x, y, z, color = self.build_queue.popleft()
  119.             if color != last_color:
  120.                 self.protocol.set_block_color(color)
  121.                 last_color = color
  122.             if self.protocol.build_block(x, y, z, color):
  123.                 blocks_left -= 1
  124.  
  125. class Tentacle(Animated):
  126.     dead = False
  127.     dying = False
  128.     on_death = None
  129.     on_removed = None
  130.     parent = None
  131.     protocol = None
  132.     origin = None
  133.     up = None
  134.     orientation = None
  135.     start_orientation = None
  136.     target_orientation = None
  137.     lerp_t = None
  138.     facing = None
  139.     sections = None
  140.     radius = 2
  141.     spread = radius / 2.0
  142.     follow = None
  143.     follow_interval = 1.3
  144.     follow_timer = follow_interval
  145.     initial_growth_interval = 0.2
  146.     growth_interval = initial_growth_interval
  147.     growth_timer = growth_interval
  148.     blocks_destroyed = None
  149.     last_block_destroyed = None
  150.     growing = True
  151.     withdraw = False
  152.     grabbed_player = None
  153.     max_hp = None
  154.    
  155.     def __init__(self, protocol, parent, (x, y, z)):
  156.         Animated.__init__(self, protocol)
  157.         self.parent = parent
  158.         self.origin = Vertex3(x, y, z)
  159.         self.up = Vertex3(0.0, 0.0, -1.0)
  160.         self.orientation = Quaternion()
  161.         self.facing = self.orientation.transform_vector(self.up)
  162.         self.sections = []
  163.         self.blocks_destroyed = []
  164.         self.parent.tentacles.append(self)
  165.         self.find_target()
  166.    
  167.     def find_target(self):
  168.         best = None
  169.         best_dist = None
  170.         best_followed = None
  171.         for player in self.protocol.players.values():
  172.             if not is_valid_enemy(player):
  173.                 continue
  174.             dist = distance_3d_vector(player.world_object.position, self.origin)
  175.             followed = self.parent.is_enemy_targeted(player)
  176.             if not best or dist < best_dist or best_followed and not followed:
  177.                 best, best_dist, best_followed = player, dist, followed
  178.         self.follow = best
  179.    
  180.     def think(self, dt):
  181.         tip = self.sections and self.sections[-1][0] or self.origin
  182.        
  183.         self.follow_timer -= dt
  184.         if self.follow and not is_valid_enemy(self.follow):
  185.             self.find_target()
  186.             if not self.follow:
  187.                 self.growth_timer = 0.66
  188.                 self.growing = False
  189.                 self.withdraw = True
  190.         elif self.follow and self.follow_timer <= 0.0:
  191.             self.follow_timer = self.follow_interval
  192.             follow_pos = self.follow.world_object.position
  193.             direction = follow_pos - tip
  194.             q = self.facing.get_rotation_to(direction)
  195.             self.start_orientation = Quaternion(*self.orientation.get())
  196.             self.target_orientation = q * self.orientation
  197.             self.lerp_t = 0.0
  198.         if self.target_orientation and self.lerp_t <= 1.0:
  199.             self.orientation = self.start_orientation.slerp(
  200.                 self.target_orientation, self.lerp_t)
  201.             self.lerp_t += 0.02
  202.         self.facing = self.orientation.transform_vector(self.up)
  203.         self.facing.normalize()
  204.        
  205.         self.growth_timer -= dt
  206.         if self.growth_timer <= 0.0:
  207.             self.growth_timer = self.growth_interval
  208.             if self.growing and self.follow:
  209.                 tip = self.grow(tip.copy())
  210.             elif self.withdraw:
  211.                 if self.sections:
  212.                     pos, blocks = self.sections.pop()
  213.                     tip = pos
  214.                     for uvw in blocks:
  215.                         if not self.parent.is_location_inside(uvw, skip = self):
  216.                             self.protocol.remove_block(*uvw)
  217.                         self.blocks.discard(uvw)
  218.                 else:
  219.                     for uvw in self.blocks:
  220.                         if not self.parent.is_location_inside(uvw, skip = self):
  221.                             self.protocol.remove_block(*uvw)
  222.                     self.dead = True
  223.                     if self.on_removed:
  224.                         self.on_removed(self)
  225.        
  226.         player = self.grabbed_player
  227.         if player:
  228.             if self.dead or not player.world_object or player.world_object.dead:
  229.                 player.grabbed_by = None
  230.                 self.grabbed_player = None
  231.             else:
  232.                 player.set_location((tip.x, tip.y, tip.z - 1.0))
  233.                 if tip.z >= 63:
  234.                     player.got_water_damage = True
  235.                     player.kill(type = FALL_KILL)
  236.                     player.got_water_damage = False
  237.    
  238.     def on_block_destroy(self, x, y, z, mode):
  239.         if mode == SPADE_DESTROY and (x, y, z) in self.blocks:
  240.             return False
  241.    
  242.     def on_block_removed(self, x, y, z):
  243.         xyz = (x, y, z)
  244.         if xyz not in self.blocks:
  245.             return
  246.         self.blocks.discard(xyz)
  247.         total_damage = 0.0
  248.         for u, v, w in self.blocks_destroyed:
  249.             xu, yv, zw = x - u, y - v, z - w
  250.             d = sqrt(xu*xu + yv*yv + zw*zw)
  251.             total_damage += d >= 1.0 and 1.0 / d or 1.0
  252.             if total_damage > self.max_hp:
  253.                 self.fracture(x, y, z)
  254.                 self.last_block_destroyed = None
  255.                 self.die()
  256.                 if self.on_death:
  257.                     self.on_death(self)
  258.                 return
  259.         if self.last_block_destroyed:
  260.             self.protocol.set_block_color(KRAKEN_BLOOD)
  261.             u, v, w = self.last_block_destroyed
  262.             self.protocol.build_block(u, v, w, KRAKEN_BLOOD)
  263.             self.blocks.add(self.last_block_destroyed)
  264.         self.last_block_destroyed = xyz
  265.         self.blocks_destroyed.append(xyz)
  266.    
  267.     def die(self):
  268.         self.follow = None
  269.         self.target_orientation = None
  270.         if self.grabbed_player:
  271.             self.grabbed_player.grabbed_by = None
  272.         self.grabbed_player = None
  273.         self.growth_timer = 0.66
  274.         speedup = 2.0 + max(len(self.sections) / 140.0, 1.0)
  275.         self.growth_interval = self.initial_growth_interval / speedup
  276.         self.growing = False
  277.         self.withdraw = True
  278.         self.dying = True
  279.    
  280.     def fracture(self, x, y, z):
  281.         protocol = self.protocol
  282.         radius = self.radius
  283.         for uvw in sphere(int(radius * 1.5), x, y, z):
  284.             if not self.parent.is_location_inside(uvw, skip = self):
  285.                 protocol.remove_block(*uvw)
  286.             self.blocks.discard(uvw)
  287.         to_remove = []
  288.         breakpoint = False
  289.         while self.sections:
  290.             pos, blocks = self.sections.pop()
  291.             for uvw in blocks:
  292.                 if not self.parent.is_location_inside(uvw, skip = self):
  293.                     if breakpoint:
  294.                         protocol.remove_block(*uvw)
  295.                     else:
  296.                         to_remove.append(uvw)
  297.                 self.blocks.discard(uvw)
  298.             if breakpoint:
  299.                 break
  300.             i, j, k = pos.get()
  301.             breakpoint = aabb_centered(x, y, z, i, j, k, radius)
  302.         if self.sections:
  303.             self.sections.pop()
  304.         for u, v, w in to_remove:
  305.             protocol.remove_block(u, v, w)
  306.    
  307.     def grow(self, tip):
  308.         if self.sections:
  309.             tip += self.facing * self.spread
  310.         map = self.protocol.map
  311.         radius = self.radius
  312.         ix, iy, iz = int(tip.x), int(tip.y), int(tip.z)
  313.         blocks = []
  314.         destroyed = 0
  315.         for x, y, z in sphere(radius, ix, iy, iz):
  316.             if (x < 0 or x >= 512 or y < 0 or y >= 512 or
  317.                 z < 0 or z >= 63):
  318.                 continue
  319.             xyz = (x, y, z)
  320.             if xyz not in self.blocks:
  321.                 if not map.get_solid(x, y, z):
  322.                     blocks.append(xyz)
  323.                     self.blocks.add(xyz)
  324.                     self.build_queue.append(xyz + (KRAKEN_BLACK,))
  325.                 elif not self.parent.is_location_inside(xyz, skip = self):
  326.                     destroyed += 1
  327.         if destroyed >= radius:
  328.             for x, y, z in sphere(radius + 2, ix, iy, iz, min_r = radius):
  329.                 if self.parent.is_location_inside((x, y, z)):
  330.                     continue
  331.                 self.protocol.remove_block(x, y, z)
  332.             self.protocol.create_explosion_effect(tip)
  333.         for player in self.protocol.players.values():
  334.             if not is_valid_enemy(player):
  335.                 continue
  336.             pos = player.world_object.position
  337.             if vector_collision(pos, tip, radius * 0.75):
  338.                 self.follow = None
  339.                 self.target_orientation = None
  340.                 self.growth_timer = 0.4
  341.                 self.growing = False
  342.                 self.withdraw = True
  343.                 self.grabbed_player = player
  344.                 player.grabbed_by = self
  345.                 player.set_location((tip.x, tip.y, tip.z - 1.0))
  346.                 player.hit(GRAB_DAMAGE)
  347.                 break
  348.         self.sections.append((tip, blocks))
  349.         return tip
  350.  
  351. class Eye():
  352.     parent = None
  353.     protocol = None
  354.     dead = False
  355.     blocks = None
  356.     origin_x = None
  357.     pos = None
  358.     base = None
  359.     hits = None
  360.     look_interval_min = 0.8
  361.     look_interval_max = 2.5
  362.     look_timer = look_interval_max
  363.     on_hit = None
  364.     create_call = None
  365.    
  366.     def __init__(self, parent, base, ox, oy, oz, hits = 3):
  367.         self.parent = parent
  368.         self.protocol = parent.protocol
  369.         self.blocks = set()
  370.         self.pos = parent.origin.copy().translate(ox, oy, oz)
  371.         self.origin_x = self.pos.x
  372.         self.base = base[:]
  373.         self.hits = hits
  374.         parent.eyes.append(self)
  375.    
  376.     def think(self, dt):
  377.         if not self.blocks:
  378.             return
  379.         self.look_timer -= dt
  380.         if self.look_timer <= 0.0:
  381.             self.look_timer = uniform(self.look_interval_min,
  382.                 self.look_interval_max)
  383.             old_x = self.pos.x
  384.             self.pos.x = max(self.origin_x - 1, min(self.origin_x + 1,
  385.                 self.pos.x + choice([-1, 1])))
  386.             if old_x != self.pos.x:
  387.                 old_blocks = self.blocks
  388.                 self.blocks = set()
  389.                 self.create_instant()
  390.                 old_blocks -= self.blocks
  391.                 self.protocol.set_block_color(KRAKEN_BLACK)
  392.                 for x, y, z in old_blocks:
  393.                     self.protocol.build_block(x, y, z, KRAKEN_BLACK,
  394.                         force = True)
  395.    
  396.     def create(self, block_queue = None, close = False):
  397.         if block_queue is None:
  398.             block_queue = deque(self.base)
  399.         last_color = None
  400.         x, y, z = self.pos.get()
  401.         x_d = None
  402.         while block_queue:
  403.             u, v, w, color = block_queue[0]
  404.             if x_d is None:
  405.                 x_d = abs(u)
  406.             elif abs(u) != x_d:
  407.                 break
  408.             if color != last_color:
  409.                 self.protocol.set_block_color(color)
  410.                 last_color = color
  411.             u, v, w = x + u, y + v, z + w
  412.             uvw = (u, v, w)
  413.             self.protocol.build_block(u, v, w, color, force = True)
  414.             if not close:
  415.                 self.parent.head.discard(uvw)
  416.                 self.blocks.add(uvw)
  417.             block_queue.popleft()
  418.         if block_queue:
  419.             self.create_call = reactor.callLater(0.25, self.create, block_queue)
  420.    
  421.     def create_instant(self, block_list = None):
  422.         if block_list is None:
  423.             block_list = self.base
  424.         last_color = None
  425.         x, y, z = self.pos.get()
  426.         block_list = sorted(block_list, key = itemgetter(3))
  427.         for u, v, w, color in block_list:
  428.             if color != last_color:
  429.                 self.protocol.set_block_color(color)
  430.                 last_color = color
  431.             u, v, w = x + u, y + v, z + w
  432.             uvw = (u, v, w)
  433.             self.protocol.build_block(u, v, w, color, force = True)
  434.             self.parent.head.discard(uvw)
  435.             self.blocks.add(uvw)
  436.    
  437.     def on_block_removed(self, x, y, z):
  438.         xyz = (x, y, z)
  439.         if self.dead or xyz not in self.blocks:
  440.             return
  441.         protocol = self.protocol
  442.         protocol.create_explosion_effect(Vertex3(x, y, z))
  443.         self.parent.build_queue.append((x, y, z, KRAKEN_BLOOD))
  444.         self.hits -= 1
  445.         if self.hits > 0:
  446.             self.pain()
  447.             uvw = (x - self.pos.x, y - self.pos.y, z - self.pos.z)
  448.             i = [uvwc[:-1] for uvwc in self.base].index(uvw)
  449.             self.base[i] = uvw + (KRAKEN_BLOOD,)
  450.         else:
  451.             self.close()
  452.             self.dead = True
  453.         if self.on_hit:
  454.             self.on_hit(self)
  455.    
  456.     def close(self):
  457.         self.parent.head.update(self.blocks)
  458.         self.blocks.clear()
  459.         if self.create_call and self.create_call.active():
  460.             self.create_call.cancel()
  461.         reactor.callLater(0.5, self.create, deque(KRAKEN_EYE_SMALL_CLOSED),
  462.             close = True)
  463.    
  464.     def pain(self):
  465.         self.close()
  466.         reactor.callLater(EYE_PAIN_TIME, self.create)
  467.         self.look_timer = EYE_PAIN_TIME + self.look_interval_min
  468.  
  469. class Kraken(Animated):
  470.     dead = False
  471.     origin = None
  472.     tentacles = None
  473.     head = None
  474.     eyes = None
  475.     max_hp = 10.0
  476.     hp = max_hp
  477.     size = 7
  478.     on_last_tentacle_death = None
  479.     on_death = None
  480.     on_removed = None
  481.     finally_call = None
  482.     phase = 0
  483.    
  484.     def __init__(self, protocol, (x, y, z)):
  485.         Animated.__init__(self, protocol)
  486.         self.origin = Vertex3(x, y, z)
  487.         self.head = set()
  488.         self.eyes = []
  489.         self.tentacles = []
  490.    
  491.     def is_location_inside(self, location, skip = None):
  492.         if location in self.head:
  493.             return True
  494.         for eye in self.eyes:
  495.             if location in eye.blocks:
  496.                 return True
  497.         for t in self.tentacles:
  498.             if t is not skip and location in t.blocks:
  499.                 return True
  500.         return False
  501.    
  502.     def is_enemy_targeted(self, player):
  503.         for t in self.tentacles:
  504.             if t.follow is player:
  505.                 return True
  506.         return False
  507.    
  508.     def on_block_destroy(self, x, y, z, mode):
  509.         for t in self.tentacles:
  510.             if t.on_block_destroy(x, y, z, mode) == False:
  511.                 return False
  512.    
  513.     def on_block_removed(self, x, y, z):
  514.         eye_died = False
  515.         for eye in self.eyes:
  516.             eye.on_block_removed(x, y, z)
  517.             eye_died = eye_died or eye.dead
  518.         if eye_died:
  519.             self.eyes = [eye for eye in self.eyes if not eye.dead]
  520.             if not self.eyes:
  521.                 self.die()
  522.        
  523.         for t in self.tentacles:
  524.             t.on_block_removed(x, y, z)
  525.    
  526.     def die(self):
  527.         protocol = self.protocol
  528.         def remove(this, remover, blocks):
  529.             if blocks:
  530.                 remover(*blocks.pop())
  531.                 reactor.callLater(0.01, this, this, remover, blocks)
  532.             elif self.on_removed:
  533.                 self.on_removed(self)
  534.         def explode(this, effect, blocks, left):
  535.             x = self.origin.x + uniform(-5.0, 5.0)
  536.             y = self.origin.y + self.size + 1.0
  537.             z = self.origin.z + uniform(-15.0, 0.0)
  538.             effect(Vertex3(x, y, z))
  539.             if not blocks or left <= 0:
  540.                 return
  541.             delay = uniform(0.3, 0.8)
  542.             left -= 1
  543.             reactor.callLater(delay, this, this, effect, blocks, left)
  544.         remove(remove, protocol.remove_block, self.head)
  545.         explode(explode, protocol.create_explosion_effect, self.head, 10)
  546.        
  547.         self.dead = True
  548.         for t in self.tentacles:
  549.             t.die()        
  550.         if self.on_death:
  551.             self.on_death(self)
  552.    
  553.     def think(self, dt):
  554.         for eye in self.eyes:
  555.             eye.think(dt)
  556.        
  557.         rebuild_list = False
  558.         for t in self.tentacles:
  559.             t.think(dt)
  560.             rebuild_list = rebuild_list or t.dead
  561.         if rebuild_list:
  562.             self.tentacles = [t for t in self.tentacles if not t.dead]
  563.             if not self.tentacles and self.on_last_tentacle_death:
  564.                 self.on_last_tentacle_death(self)
  565.    
  566.     def hit(self, value, rate):
  567.         hp_bar = self.protocol.hp_bar
  568.         if not hp_bar.shown:
  569.             hp_bar.progress = 1.0 - self.hp / self.max_hp
  570.             hp_bar.show()
  571.         self.hp = max(self.hp - value, 0)
  572.         previous_rate = hp_bar.rate
  573.         hp_bar.get_progress(True)
  574.         hp_bar.rate = rate
  575.         hp_bar.update_rate()
  576.         hp_bar.send_progress()
  577.         target_progress = 1.0 - self.hp / self.max_hp
  578.         delay = (target_progress - hp_bar.progress) / hp_bar.rate_value
  579.         hp_call = hp_bar.hp_call
  580.         if hp_call and hp_call.active():
  581.             if previous_rate == 0:
  582.                 hp_call.cancel()
  583.             else:
  584.                 hp_call.reset(delay)
  585.                 return
  586.         hp_bar.hp_call = reactor.callLater(delay, hp_bar.stop)
  587.    
  588.     def create_head(self, head_list, height = None):
  589.         height = height or len(head_list)
  590.         x, y, z = self.origin.get()
  591.         for d in head_list[-height:]:
  592.             for u, v in d:
  593.                 xyzc = (x + u, y + v, z, KRAKEN_BLACK)
  594.                 self.build_queue.append(xyzc)
  595.                 self.head.add(xyzc[:-1])
  596.             z -= 1
  597.         if height < len(head_list):
  598.             delay = 0.6
  599.             reactor.callLater(delay, self.create_head, head_list, height + 6)
  600.  
  601. force_boss = False
  602.  
  603. @admin
  604. def kraken(connection, value = None):
  605.     global force_boss
  606.     protocol = connection.protocol
  607.     if protocol.game_mode != TC_MODE:
  608.         return 'Unfortunately, the game mode is required to be TC. Change it then restart'
  609.     if not protocol.boss_ready:
  610.         force_boss = True
  611.         return 'The next map will be kraken-ready. Change maps then try again'
  612.     if protocol.boss:
  613.         return "There is already a kraken! Why can't I hold all these krakens?"
  614.     try:
  615.         x, y = coordinates(value)
  616.     except (ValueError):
  617.         return 'Need coordinates where to spawn the kraken, e.g /kraken E3'
  618.     start_kraken(protocol, max(x, 64), max(y, 64))
  619.  
  620. if ALLOW_KRAKEN_COMMAND:
  621.     add(kraken)
  622.  
  623. def start_kraken(protocol, x, y, hardcore = False, finally_call = None):
  624.     y += 32
  625.     boss = Kraken(protocol, (x, y - 12, 63))
  626.     protocol.boss = boss
  627.     if USE_DAYCYCLE and protocol.daycycle_loop.running:
  628.         protocol.daycycle_loop.stop()
  629.    
  630.     arena = getattr(protocol.map_info.info, 'arena', None)
  631.     if arena:
  632.         arena_center = (int((arena[2] - arena[0]) / 2.0 + arena[0]),
  633.             int((arena[3] - arena[1]) / 2.0 + arena[1]))
  634.         arena_radius = min(arena[2] - arena[0], arena[3] - arena[1]) / 2.0
  635.    
  636.     def randring():
  637.         min_r, max_r = 12.0, 32.0
  638.         r = uniform(min_r, max_r)
  639.         a = uniform(0.0, pi)
  640.         return x + cos(a) * r, y + sin(a) * r, 63
  641.    
  642.     def randring_arena():
  643.         if not arena:
  644.             return randring()
  645.         r = uniform(arena_radius, arena_radius * 1.2)
  646.         a = uniform(0.0, 2*pi)
  647.         x, y = arena_center
  648.         return x + cos(a) * r, y + sin(a) * r, 63
  649.    
  650.     def minor_hit(caller = None):
  651.         boss.hit(1.0, 1)
  652.         caller.on_removed = None
  653.    
  654.     def major_hit(caller = None):
  655.         boss.hit(3.0, 1)
  656.    
  657.     def major_hit_and_progress(caller = None):
  658.         caller.on_hit = major_hit
  659.         major_hit()
  660.         progress()
  661.    
  662.     def major_hit_and_pain(caller = None):
  663.         major_hit()
  664.         boss_alive = False
  665.         for eye in caller.parent.eyes:
  666.             if eye is not caller and not eye.dead:
  667.                 eye.pain()
  668.                 boss_alive = True
  669.         if boss_alive and caller.dead:
  670.             falling_blocks_start()
  671.    
  672.     def respawn_tentacle(caller = None):
  673.         if boss and not boss.dead:
  674.             reactor.callLater(5.0, spawn_tentacles, 1, True)
  675.    
  676.     def spawn_tentacles(amount, respawn = False, fast = False, arena = False,
  677.         no_hit = False):
  678.         if not hardcore:
  679.             toughness = max(3.0, min(10.0, len(protocol.players) * 0.5))
  680.         else:
  681.             toughness = max(5.0, min(13.0, len(protocol.players) * 0.85))
  682.         if boss and not boss.dead:
  683.             for i in xrange(amount):
  684.                 origin = randring_arena() if arena else randring()
  685.                 t = Tentacle(protocol, boss, origin)
  686.                 t.max_hp = toughness
  687.                 t.growth_timer = uniform(i * 1.0, i * 1.2)
  688.                 if hardcore:
  689.                     t.initial_growth_interval *= 0.8
  690.                 if fast:
  691.                     t.initial_growth_interval *= 0.5
  692.                 else:
  693.                     t.follow_timer = 2.0
  694.                 t.growth_interval = t.initial_growth_interval
  695.                 if respawn:
  696.                     t.on_removed = respawn_tentacle
  697.                 elif not no_hit:
  698.                     t.on_death = minor_hit
  699.                     t.on_removed = minor_hit
  700.    
  701.     def falling_blocks_cycle():
  702.         alive_players = filter(is_valid_enemy, protocol.players.values())
  703.         if not alive_players:
  704.             return
  705.         player = choice(alive_players)
  706.         x, y, z = player.world_object.position.get()
  707.         protocol.create_falling_block(int(x), int(y), randrange(2, 4), 2)
  708.    
  709.     def falling_blocks_start():
  710.         for i in range(20):
  711.             reactor.callLater(i * 0.4, falling_blocks_cycle)
  712.    
  713.     def squid_head():
  714.         h = []
  715.         for i in xrange(37, 5, -2):
  716.             h.append(list(disc(i, min_rr = i - 15)))
  717.         return h
  718.    
  719.     def squid_head_large():
  720.         h = []
  721.         for i in xrange(42, 3, -2):
  722.             ii = int(i ** 1.3)
  723.             h.append(list(disc(ii, y = int(sqrt(i)), min_rr = i + 10)))
  724.         return h
  725.    
  726.     def regenerate_players():
  727.         for player in protocol.players.values():
  728.             player.trapped = False
  729.             player.last_hit = reactor.seconds()
  730.             player.regenerating = True
  731.             if not player.world_object.dead:
  732.                 player.regen_loop.start(REGEN_FREQUENCY)
  733.             else:
  734.                 player.spawn(player.world_object.position.get())
  735.    
  736.     def round_end(caller = None):
  737.         regenerate_players()
  738.         reactor.callLater(8.0, progress)
  739.    
  740.     def round_end_delay(caller = None):
  741.         reactor.callLater(10.0, round_end)
  742.    
  743.     def round_start(caller = None):
  744.         for player in protocol.players.values():
  745.             player.regenerating = False
  746.    
  747.     def progress_delay(caller = None):
  748.         reactor.callLater(6.0, progress)
  749.    
  750.     def victory(caller = None):
  751.         regenerate_players()
  752.         if USE_DAYCYCLE:
  753.             protocol.current_time = 23.30
  754.             protocol.update_day_color()
  755.    
  756.     def cleanup(caller = None):
  757.         round_start()
  758.         protocol.boss = None
  759.         if USE_DAYCYCLE and protocol.daycycle_loop.running:
  760.             protocol.daycycle_loop.stop()
  761.         if caller.finally_call:
  762.             caller.finally_call(caller)
  763.    
  764.     def red_sky():
  765.         if USE_DAYCYCLE:
  766.             protocol.day_colors = [
  767.                 ( 0.00, (0.5527, 0.24, 0.94), False),
  768.                 ( 0.10, (0.0,    0.05, 0.05), True),
  769.                 ( 0.20, (0.0,    1.00, 0.34), False),
  770.                 (23.30, (0.0,    1.00, 0.34), False),
  771.                 (23.50, (0.5527, 0.24, 0.94), False)]
  772.             protocol.current_time = 0.00
  773.             protocol.target_color_index = 0
  774.             protocol.update_day_color()
  775.             if not protocol.daycycle_loop.running:
  776.                 protocol.daycycle_loop.start(protocol.day_update_frequency)
  777.    
  778.     progress = None
  779.    
  780.     def progress_normal(caller = None):
  781.         boss.phase += 1
  782.         round_start()
  783.        
  784.         if boss.phase == 1:
  785.             boss.on_last_tentacle_death = progress_delay
  786.             spawn_tentacles(2)
  787.         elif boss.phase == 2:
  788.             boss.on_last_tentacle_death = round_end
  789.             spawn_tentacles(4)
  790.         elif boss.phase == 3:
  791.             boss.on_last_tentacle_death = round_end
  792.             spawn_tentacles(3, fast = True)
  793.         elif boss.phase == 4:
  794.             boss.on_last_tentacle_death = None
  795.             boss.on_death = round_end_delay
  796.             boss.size = 7
  797.             boss.create_head(squid_head())
  798.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 5, -1, hits = 5)
  799.             eye.on_hit = major_hit_and_progress
  800.             reactor.callLater(7.0, eye.create)
  801.         elif boss.phase == 5:
  802.             spawn_tentacles(3, respawn = True)
  803.             spawn_tentacles(2, arena = True, no_hit = True)
  804.         elif boss.phase == 6:
  805.             protocol.send_chat('LOOK UP!', global_message = None)
  806.             falling_blocks_start()
  807.             reactor.callLater(15.0, round_end)
  808.         elif boss.phase == 7:
  809.             boss.dead = False
  810.             boss.on_last_tentacle_death = round_end
  811.             spawn_tentacles(4, fast = True, arena = True)
  812.         elif boss.phase == 8:
  813.             red_sky()
  814.             boss.on_last_tentacle_death = None
  815.             boss.on_death = victory
  816.             boss.on_removed = cleanup
  817.             boss.finally_call = finally_call
  818.             boss.origin.y -= 24
  819.             boss.size = 16
  820.             boss.create_head(squid_head_large())
  821.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 16, -2, hits = 4)
  822.             eye.on_hit = major_hit_and_pain
  823.             reactor.callLater(16.0, eye.create)
  824.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 14, -6, hits = 4)
  825.             eye.on_hit = major_hit_and_pain
  826.             reactor.callLater(16.0, eye.create)
  827.             reactor.callLater(18.0, spawn_tentacles, 5, respawn = True)
  828.    
  829.     def progress_hardcore(caller = None):
  830.         boss.phase += 1
  831.         round_start()
  832.        
  833.         if boss.phase == 1:
  834.             boss.on_last_tentacle_death = progress_delay
  835.             spawn_tentacles(3)
  836.             falling_blocks_start()
  837.         elif boss.phase == 2:
  838.             boss.on_last_tentacle_death = round_end
  839.             spawn_tentacles(4, fast = True)
  840.         elif boss.phase == 3:
  841.             boss.on_last_tentacle_death = None
  842.             boss.on_death = round_end_delay
  843.             boss.size = 7
  844.             boss.create_head(squid_head())
  845.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 5, -1, hits = 8)
  846.             eye.look_interval_min *= 0.8
  847.             eye.look_interval_max *= 0.6
  848.             eye.on_hit = major_hit_and_progress
  849.             reactor.callLater(7.0, eye.create)
  850.         elif boss.phase == 4:
  851.             spawn_tentacles(3, respawn = True)
  852.             spawn_tentacles(3, arena = True, no_hit = True)
  853.         elif boss.phase == 5:
  854.             boss.dead = False
  855.             boss.on_last_tentacle_death = round_end
  856.             spawn_tentacles(5, fast = True, arena = True)
  857.         elif boss.phase == 6:
  858.             red_sky()
  859.             boss.on_last_tentacle_death = None
  860.             boss.on_death = victory
  861.             boss.on_removed = cleanup
  862.             boss.finally_call = finally_call
  863.             boss.origin.y -= 24
  864.             boss.size = 16
  865.             boss.create_head(squid_head_large())
  866.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 16, -2, hits = 6)
  867.             eye.look_interval_min *= 0.8
  868.             eye.look_interval_max *= 0.6
  869.             eye.on_hit = major_hit_and_pain
  870.             reactor.callLater(16.0, eye.create)
  871.             eye = Eye(boss, KRAKEN_EYE_SMALL, 0, 14, -6, hits = 6)
  872.             eye.look_interval_min *= 0.8
  873.             eye.look_interval_max *= 0.6
  874.             eye.on_hit = major_hit_and_pain
  875.             reactor.callLater(16.0, eye.create)
  876.             reactor.callLater(18.0, spawn_tentacles, 5, respawn = True)
  877.             reactor.callLater(14.0, falling_blocks_start)
  878.    
  879.     boss.blocks_per_cycle = 2
  880.     boss.build_interval = 0.01
  881.     if not hardcore:
  882.         progress = progress_normal
  883.         boss.hp = boss.max_hp = 2.0 + 4.0 + 3.0 + 5*3.0 + 4.0 + (4 + 4)*3.0
  884.     else:
  885.         progress = progress_hardcore
  886.         boss.hp = boss.max_hp = 3.0 + 4.0 + 8*3.0 + 5.0 + (6 + 6)*3.0
  887.     progress()
  888.     return boss
  889.  
  890. class BossTerritory(Territory):
  891.     shown = False
  892.     hp_call = None
  893.    
  894.     def add_player(self, player):
  895.         return
  896.    
  897.     def remove_player(self, player):
  898.         return
  899.    
  900.     def update_rate(self):
  901.         self.rate_value = self.rate * TC_CAPTURE_RATE
  902.         self.capturing_team = (self.rate_value < 0 and
  903.             self.protocol.blue_team or self.protocol.green_team)
  904.         self.start = reactor.seconds()
  905.    
  906.     def show(self):
  907.         self.shown = True
  908.         for player in self.protocol.players.values():
  909.             self.update_for_player(player)
  910.    
  911.     def hide(self):
  912.         self.shown = False
  913.         self.update()
  914.    
  915.     def stop(self):
  916.         self.rate = 0
  917.         self.get_progress(True)
  918.         self.update_rate()
  919.         self.send_progress()
  920.         self.hp_call = reactor.callLater(3.0, self.hide)
  921.    
  922.     def update_for_player(self, connection, orientation = None):
  923.         x, y, z = orientation or connection.world_object.orientation.get()
  924.         v = Vertex3(x, y, 0.0)
  925.         v.normalize()
  926.         v *= -10.0
  927.         v += connection.world_object.position
  928.         move_object.object_type = self.id
  929.         move_object.state = self.team and self.team.id or NEUTRAL_TEAM
  930.         move_object.x = v.x
  931.         move_object.y = v.y
  932.         move_object.z = v.z
  933.         connection.send_contained(move_object)
  934.  
  935. def apply_script(protocol, connection, config):
  936.     class BossProtocol(protocol):
  937.         game_mode = TC_MODE
  938.        
  939.         boss = None
  940.         boss_ready = False
  941.         hp_bar = None
  942.        
  943.         def start_kraken(self, x, y, hardcore = False, finally_call = None):
  944.             return start_kraken(self, x, y, hardcore, finally_call)
  945.        
  946.         def is_indestructable(self, x, y, z):
  947.             if self.boss:
  948.                 if self.boss.head and (x, y, z) in self.boss.head:
  949.                     return True
  950.             return protocol.is_indestructable(self, x, y, z)
  951.        
  952.         def on_world_update(self):
  953.             if self.boss:
  954.                 self.boss.think(UPDATE_FREQUENCY)
  955.             protocol.on_world_update(self)
  956.        
  957.         def on_map_change(self, map):
  958.             self.boss = None
  959.             self.boss_ready = False
  960.             self.hp_bar = None
  961.             protocol.on_map_change(self, map)
  962.        
  963.         def get_cp_entities(self):
  964.             global force_boss
  965.             if force_boss or getattr(self.map_info.info, 'boss', False):
  966.                 if (USE_DAYCYCLE and self.daycycle_loop and
  967.                     self.daycycle_loop.running):
  968.                     self.daycycle_loop.stop()
  969.                 force_boss = False
  970.                 self.boss_ready = True
  971.                 self.hp_bar = BossTerritory(0, self, 0.0, 0.0, 0.0)
  972.                 self.hp_bar.team = self.green_team
  973.                 return [self.hp_bar]
  974.             return protocol.get_cp_entities(self)
  975.        
  976.         def create_explosion_effect(self, position):
  977.             self.world.create_object(Grenade, 0.0, position, None,
  978.                 Vertex3(), None)
  979.             grenade_packet.value = 0.0
  980.             grenade_packet.player_id = 32
  981.             grenade_packet.position = position.get()
  982.             grenade_packet.velocity = (0.0, 0.0, 0.0)
  983.             self.send_contained(grenade_packet)
  984.        
  985.         def falling_block_collide(self, x, y, z, size):
  986.             if not self.map.get_solid(x, y, z):
  987.                 new_z = self.map.get_z(x, y)
  988.                 if new_z > z:
  989.                     remaining = fall_eta(new_z - z)
  990.                     reactor.callLater(remaining, self.falling_block_collide,
  991.                         x, y, new_z, size)
  992.                     return
  993.             for player in self.players.values():
  994.                 i, j, k = player.world_object.position.get()
  995.                 s = size + 3.0
  996.                 if aabb(i, j, k, x - 1.5, y - 1.5, z - 5.0, s, s, 6.0):
  997.                     player.hit(FALLING_BLOCK_DAMAGE, type = FALL_KILL)
  998.             half_size = int(ceil(size / 2.0))
  999.             ox, oy = x - half_size, y - half_size
  1000.             for u, v, w in prism(ox, oy, z - 1, size, size, 3):
  1001.                 self.remove_block(u, v, w, user = True)
  1002.             self.create_explosion_effect(Vertex3(x, y, z))
  1003.        
  1004.         def create_falling_block(self, x, y, size, height):
  1005.             self.set_block_color(FALLING_BLOCK_COLOR)
  1006.             half_size = int(ceil(size / 2.0))
  1007.             ox, oy = x - half_size, y - half_size
  1008.             for u, v, w in prism(ox, oy, FALLING_BLOCK_Z, size, size, height):
  1009.                 self.build_block(u, v, w, FALLING_BLOCK_COLOR)
  1010.             self.remove_block(ox, oy, FALLING_BLOCK_Z)
  1011.            
  1012.             z = self.map.get_z(x, y)
  1013.             eta = fall_eta(z - FALLING_BLOCK_Z)
  1014.             reactor.callLater(eta, self.falling_block_collide, x, y, z, size)
  1015.        
  1016.         def set_block_color(self, color):
  1017.             set_color.value = color
  1018.             set_color.player_id = 32
  1019.             self.send_contained(set_color, save = True)
  1020.        
  1021.         def remove_block(self, x, y, z, user = False):
  1022.             if z >= 63:
  1023.                 return False
  1024.             if not self.map.remove_point(x, y, z, user):
  1025.                 return False
  1026.             block_action.value = DESTROY_BLOCK
  1027.             block_action.player_id = 32
  1028.             block_action.x = x
  1029.             block_action.y = y
  1030.             block_action.z = z
  1031.             self.send_contained(block_action, save = True)
  1032.             return True
  1033.        
  1034.         def build_block(self, x, y, z, color, force = False):
  1035.             if force:
  1036.                 self.remove_block(x, y, z)
  1037.             if not self.map.get_solid(x, y, z):
  1038.                 self.map.set_point_unsafe_int(x, y, z, color)
  1039.                 block_action.value = BUILD_BLOCK
  1040.                 block_action.player_id = 32
  1041.                 block_action.x = x
  1042.                 block_action.y = y
  1043.                 block_action.z = z
  1044.                 self.send_contained(block_action, save = True)
  1045.                 return True
  1046.             return False
  1047.    
  1048.     class BossConnection(connection):
  1049.         regenerating = False
  1050.         trapped = False
  1051.         got_water_damage = False
  1052.         grabbed_by = None
  1053.         last_hit = None
  1054.         regen_loop = None
  1055.        
  1056.         def __init__(self, *arg, **kw):
  1057.             connection.__init__(self, *arg, **kw)
  1058.             self.regen_loop = LoopingCall(self.regen_cycle)
  1059.        
  1060.         def regen_cycle(self):
  1061.             if (not self.regenerating or self.god or
  1062.                 self.world_object is None or self.world_object.dead):
  1063.                 self.regen_loop.stop()
  1064.                 return
  1065.             last_hit = self.last_hit
  1066.             if last_hit and reactor.seconds() - last_hit < REGEN_ONSET:
  1067.                 return
  1068.             if self.hp < 100 - REGEN_AMOUNT:
  1069.                 self.set_hp(self.hp + REGEN_AMOUNT, type = FALL_KILL)
  1070.             else:
  1071.                 self.refill()
  1072.                 self.regen_loop.stop()
  1073.        
  1074.         def get_spawn_location(self):
  1075.             if self.protocol.boss and self.world_object and not self.trapped:
  1076.                 return self.world_object.position.get()
  1077.             return connection.get_spawn_location(self)
  1078.        
  1079.         def get_respawn_time(self):
  1080.             if self.protocol.boss:
  1081.                 return 2 if self.trapped else RESPAWN_TIME
  1082.             return connection.get_respawn_time(self)
  1083.        
  1084.         def on_spawn(self, pos):
  1085.             if self.trapped:
  1086.                 self.send_chat('You were eaten by a giant squid :( Pray your '
  1087.                     'friends can get you out of this one.')
  1088.                 self.set_location(self.protocol.boss.origin.get())
  1089.             return connection.on_spawn(self, pos)
  1090.        
  1091.         def on_reset(self):
  1092.             self.regenerating = False
  1093.             self.trapped = False
  1094.             self.got_water_damage = False
  1095.             self.grabbed_by = None
  1096.             self.last_hit = None
  1097.             if self.regen_loop and self.regen_loop.running:
  1098.                 self.regen_loop.stop()
  1099.             connection.on_reset(self)
  1100.        
  1101.         def on_disconnect(self):
  1102.             if self.regen_loop and self.regen_loop.running:
  1103.                 self.regen_loop.stop()
  1104.             self.regen_loop = None
  1105.             connection.on_disconnect(self)
  1106.        
  1107.         def on_kill(self, by = None):
  1108.             if self.protocol.boss:
  1109.                 if self.grabbed_by:
  1110.                     self.grabbed_by.grabbed_player = None
  1111.                 self.grabbed_by = None
  1112.                 if (self.trapped or self.got_water_damage and
  1113.                     self.protocol.boss and not self.protocol.boss.dead and
  1114.                     self.protocol.boss.head):
  1115.                     self.trapped = True
  1116.                 else:
  1117.                     self.send_chat('You died! Yell at your friends to walk '
  1118.                         'over you to revive you.')
  1119.             connection.on_kill(self, by)
  1120.        
  1121.         def on_weapon_set(self, value):
  1122.             if self.protocol.boss and self.regenerating:
  1123.                 self.weapon = value
  1124.                 self.set_weapon(self.weapon, no_kill = True)
  1125.                 self.spawn(self.world_object.position.get())
  1126.                 return False
  1127.             return connection.on_weapon_set(self, value)
  1128.        
  1129.         def on_orientation_update(self, x, y, z):
  1130.             if self.protocol.hp_bar and self.protocol.hp_bar.shown:
  1131.                 self.protocol.hp_bar.update_for_player(self, (x, y, z))
  1132.             connection.on_orientation_update(self, x, y, z)
  1133.        
  1134.         def on_position_update(self):
  1135.             if not self.protocol.boss_ready:
  1136.                 connection.on_position_update(self)
  1137.                 return
  1138.             if is_valid_enemy(self) and self.world_object.position.z >= 61:
  1139.                 self.got_water_damage = True
  1140.                 self.hit(WATER_DAMAGE)
  1141.                 self.got_water_damage = False
  1142.             if (not self.world_object.dead and not self.grabbed_by
  1143.                 and not self.trapped):
  1144.                 for player in self.protocol.players.values():
  1145.                     if player is not self and player.world_object.dead:
  1146.                         pos = player.world_object.position
  1147.                         if vector_collision(self.world_object.position, pos):
  1148.                             player.spawn(pos.get())
  1149.             if self.protocol.hp_bar and self.protocol.hp_bar.shown:
  1150.                 self.protocol.hp_bar.update_for_player(self)
  1151.             connection.on_position_update(self)
  1152.        
  1153.         def on_block_build_attempt(self, x, y, z):
  1154.             if self.trapped:
  1155.                 return False
  1156.             return connection.on_block_build(self, x, y, z)
  1157.        
  1158.         def on_block_destroy(self, x, y, z, mode):
  1159.             if self.trapped:
  1160.                 return False
  1161.             if self.protocol.boss:
  1162.                 if self.protocol.boss.on_block_destroy(x, y, z, mode) == False:
  1163.                     return False
  1164.             return connection.on_block_destroy(self, x, y, z, mode)
  1165.        
  1166.         def on_block_removed(self, x, y, z):
  1167.             if self.protocol.boss:
  1168.                 self.protocol.boss.on_block_removed(x, y, z)
  1169.             connection.on_block_removed(self, x, y, z)
  1170.        
  1171.         def on_hit(self, hit_amount, hit_player):
  1172.             self.last_hit = reactor.seconds()
  1173.             if self.regenerating and not self.regen_loop.running:
  1174.                 self.regen_loop.start(REGEN_FREQUENCY)
  1175.             if self.protocol.boss_ready:
  1176.                 if self is hit_player and self.hp:
  1177.                     if hit_amount >= self.hp:
  1178.                         return self.hp - 1
  1179.             return connection.on_hit(self, hit_amount, hit_player)
  1180.        
  1181.         def on_fall(self, damage):
  1182.             if self.grabbed_by or self.regenerating:
  1183.                 return False
  1184.             self.last_hit = reactor.seconds()
  1185.             if self.regenerating and not self.regen_loop.running:
  1186.                 self.regen_loop.start(REGEN_FREQUENCY)
  1187.             return connection.on_fall(self, damage)
  1188.    
  1189.     return BossProtocol, BossConnection
Advertisement
Add Comment
Please, Sign In to add comment