def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title('agar.io') window.set_default_size(self.win_size.x, self.win_size.y) window.connect('delete-event', Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.POINTER_MOTION_MASK) window.connect('key-press-event', self.key_pressed) window.connect('motion-notify-event', self.mouse_moved) window.connect('button-press-event', self.mouse_pressed) self.drawing_area.connect('draw', self.draw) window.show_all()
def on_draw_hud(self, c, w): for i, t in enumerate(self.draw_times): c.draw_line(w.win_size - Vec(4 * i - 2, 0), relative=(0, -t * 1000), width=2, color=to_rgba(RED, .3)) for i, t in enumerate(self.world_times): c.draw_line(w.win_size - Vec(4 * i, 0), relative=(0, -t * 1000), width=2, color=to_rgba(YELLOW, .3)) # 25, 30, 60 FPS marks graph_width = 4 * len(self.draw_times) for fps, color in ((25, ORANGE), (30, GREEN), (60, BLUE)): c.draw_line(w.win_size - Vec(graph_width, 1000 / fps), relative=(graph_width, 0), width=.5, color=to_rgba(color, .3)) now = time() dt = now - self.draw_last self.draw_last = now self.draw_times.appendleft(dt)
def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title("agar.io") window.set_default_size(self.win_size.x, self.win_size.y) window.connect("delete-event", Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.POINTER_MOTION_MASK) window.connect("key-press-event", self.key_pressed) window.connect("motion-notify-event", self.mouse_moved) window.connect("button-press-event", self.mouse_pressed) self.drawing_area.connect("draw", self.draw) window.show_all()
def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused # if self.player.is_alive or (self.player.center.x == 0 and self.player.center.y == 0) or not self.player.scale == 1.0: # HACK due to bug: player scale is sometimes wrong (sent by server?) in spectate mode window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) new_screen_scale = self.player.scale * window_scale * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) smoothing_factor = 0.1 if self.player.is_alive: smoothing_factor = 0.3 self.world_center.x = lerp_smoothing(self.world_center.x, self.player.center.x, smoothing_factor, 0.01) self.world_center.y = lerp_smoothing(self.world_center.y, self.player.center.y, smoothing_factor, 0.01) self.world = self.player.world elif self.world.size: new_screen_scale = min( self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = self.screen_zoom_scale self.world_center = Vec(0, 0)
def mouse_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.mouse_pos = Vec(event.x, event.y) pos_world = self.screen_to_world_pos(self.mouse_pos) self.input_subscriber.on_mouse_moved(pos=self.mouse_pos, pos_world=pos_world)
def on_draw_hud(self, c, w): c.draw_text((10, 30), 'Team', align='left', color=WHITE, outline=(BLACK, 2), size=27) # draw player position in main view for i, player in enumerate(list(self.tagar_client.player_list.values())): c.draw_text((10, 60 + TEAM_OVERLAY_PADDING * i), player.nick, align='left', color=WHITE, outline=(BLACK, 2), size=18) if player.total_mass > 0: mass_color = GRAY mass_text = 'Mass: ' + str('%.2f' % player.total_mass) else: mass_text = 'Dead' mass_color = RED c.draw_text((10, 75 + TEAM_OVERLAY_PADDING * i), mass_text, align='left', color=mass_color, outline=(BLACK, 2), size=12) c.draw_text((10, 88 + TEAM_OVERLAY_PADDING * i), '#' + player.party_token, align='left', color=GRAY, outline=(BLACK, 2), size=12) button = Button(90, 75 - 12 + TEAM_OVERLAY_PADDING * i, 50, 25, "JOIN") button.id = player w.register_button(button) c.draw_button(button) if self.tagar_client.player.is_alive and player.is_alive: # draw lines to team members client_pos = w.world_to_screen_pos(w.player.center) pos = w.world_to_screen_pos(Vec(player.position_x, player.position_y)) c.draw_line(client_pos, pos, width=2, color=GREEN) # TODO draw names text_size = 12 border_x = border_y = text_size if not border_x < pos.x < w.win_size.x-border_x or not border_y < pos.y < w.win_size.x-border_x: side_out = abs(pos.x/pos.y) > w.win_size.x/w.win_size.y alignment = 'left' if pos.x < w.win_size.x/2 else 'right' if side_out and abs(pos.x-client_pos.x) > 0.0: x = min(max(pos.x, border_x), w.win_size.x-border_x) pos.y -= client_pos.y pos.y *= abs((x-client_pos.x)/(pos.x-client_pos.x)) pos.y += client_pos.y pos.x = x elif abs(pos.y-client_pos.y) > 0.0: y = min(max(pos.y, border_y), w.win_size.y-border_y) pos.x -= client_pos.x pos.x *= abs((y-client_pos.y)/(pos.y-client_pos.y)) pos.x += client_pos.x pos.y = y dist = ((w.player.center.x-player.position_x)**2 + (w.player.center.y-player.position_y)**2)**0.5 c.draw_text(pos, "%s (%.1f / %.1f)" % (player.nick, player.total_mass, dist), align=alignment, color=WHITE, outline=(BLACK, 2), size=text_size)
def draw(c, w, cell, pos=None): if cell.is_food or cell.is_ejected_mass: return text_pos = Vec(pos) if pos else w.world_to_screen_pos(cell.pos) # draw cell's mass if cell.name: text_pos.iadd(Vec(0, (info_size + nick_size(cell, w)) / 2)) c.draw_text(text_pos, '%i' % cell.mass, align='center', outline=(BLACK, 2), size=info_size) # draw needed mass to eat it splitted text_pos.iadd(Vec(0, info_size)) c.draw_text(text_pos, '(%i)' % ((cell.mass*2*1.33)-w.player.total_mass), align='center', outline=(BLACK, 2), size=info_size/1.5)
def draw_minimap_backgound(self, c, w): if w.world.size: minimap_w = w.win_size.x / 5 minimap_size = Vec(minimap_w, minimap_w) minimap_scale = minimap_size.x / w.world.size.x minimap_offset = w.win_size - minimap_size def world_to_map(world_pos): pos_from_top_left = world_pos - w.world.top_left return minimap_offset + pos_from_top_left * minimap_scale # minimap background c.fill_rect(minimap_offset, size=minimap_size, color=to_rgba(DARK_GRAY, .8)) # outline the area visible in window c.stroke_rect(world_to_map(w.screen_to_world_pos(Vec(0, 0))), world_to_map(w.screen_to_world_pos(w.win_size)), width=1, color=BLACK)
def test_visible_area(self): player = Player() # simple test win = WindowMock() player.scale = win.screen_scale player.world.top_left = win.tl player.world.bottom_right = win.br ptl, pbr = player.visible_area rtl = win.screen_to_world_pos(zero) rbr = win.screen_to_world_pos(screen) self.assertAlmostEqual(ptl.x, rtl.x) self.assertAlmostEqual(ptl.y, rtl.y) self.assertAlmostEqual(pbr.x, rbr.x) self.assertAlmostEqual(pbr.y, rbr.y) # complex test win = WindowMock(Vec(123, 234), Vec(-34.5, 45.6)) player.scale = win.screen_scale = 2.3 player.world.top_left = win.tl player.world.bottom_right = win.br ptl, pbr = player.visible_area rtl = win.screen_to_world_pos(zero) rbr = win.screen_to_world_pos(screen) self.assertAlmostEqual(ptl.x, rtl.x) self.assertAlmostEqual(ptl.y, rtl.y) self.assertAlmostEqual(pbr.x, rbr.x) self.assertAlmostEqual(pbr.y, rbr.y)
def on_draw_minimap(self, c, w): if w.world.size: minimap_w = w.win_size.x / 5 minimap_size = Vec(minimap_w, minimap_w) minimap_scale = minimap_size.x / w.world.size.x minimap_offset = w.win_size - minimap_size def world_to_map(world_pos): pos_from_top_left = world_pos - w.world.top_left return minimap_offset + pos_from_top_left * minimap_scale # draw cells cells = self.tagar_client.team_world.cells.copy() for cell in cells.values(): if cell.cid not in w.world.cells: if cell.cid in self.tagar_client.team_cids: c.fill_circle(world_to_map(cell.pos), cell.size * minimap_scale, color=to_rgba(cell.color, 0.7)) else: alpha = .66 if cell.mass > (self.tagar_client.player.total_mass * 0.66) else 0.33 c.stroke_circle(world_to_map(cell.pos), cell.size * minimap_scale, color=to_rgba(cell.color, alpha)) # draw lines to team members if self.tagar_client.player.is_alive: for i, player in enumerate(list(self.tagar_client.player_list.values())): if player.is_alive: c.draw_line(world_to_map(w.player.center), world_to_map(Vec(player.position_x, player.position_y)), width=1, color=GREEN) # draw names for i, player in enumerate(list(self.tagar_client.player_list.values())): if player.is_alive: c.draw_text(world_to_map(Vec(player.position_x, player.position_y)), player.nick, align='center', color=WHITE, outline=(BLACK, 2), size=8)
def on_draw_hud(c, w): if w.player.is_alive and w.screen_zoom_scale != 1.0: window_scale = max(w.win_size.x / 1920, w.win_size.y / 1080) screen_scale_no_zoom = w.player.scale * window_scale def screen_to_world_pos(screen_pos): return (screen_pos - w.screen_center) \ .idiv(screen_scale_no_zoom).iadd(w.world_center) # outline the area visible in window c.stroke_rect( w.world_to_screen_pos(screen_to_world_pos(Vec(0, 0))), w.world_to_screen_pos(screen_to_world_pos(w.win_size)), width=1, color=LIGHT_GRAY)
def on_draw_cells(self, c, w): if len(w.player.own_ids) <= 1: return # dead or only one cell, no re-merge time to display now = time() for cell in w.player.own_cells: split_for = now - cell.spawn_time # formula by HungryBlob ttr = max(30, cell.size // 5) - split_for if ttr < 0: continue pos = w.world_to_screen_pos(cell.pos) pos.isub(Vec(0, (info_size + nick_size(cell, w)) / 2)) c.draw_text(pos, 'TTR %.1fs after %.1fs' % (ttr, split_for), align='center', outline=(BLACK, 2), size=info_size)
def on_draw_minimap(c, w): if w.world.size: minimap_w = w.win_size.x / 5 minimap_size = Vec(minimap_w, minimap_w) minimap_scale = minimap_size.x / w.world.size.x minimap_offset = w.win_size - minimap_size def world_to_map(world_pos): pos_from_top_left = world_pos - w.world.top_left return minimap_offset + pos_from_top_left * minimap_scale for cell in w.world.cells.values(): c.stroke_circle(world_to_map(cell.pos), cell.size * minimap_scale, color=to_rgba(cell.color, .8))
def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) self.screen_scale = self.player.scale * window_scale self.world_center = self.player.center self.world = self.player.world elif self.world.size: self.screen_scale = min(self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = 1 self.world_center = Vec(0, 0)
class WorldViewer(object): """ Draws one world and handles keys/mouse. Does not poll for events itself. Calls input_subscriber.on_{key_pressed|mouse_moved}() methods on key/mouse input. Calls draw_subscriber.on_draw_{background|cells|hud}() methods when drawing. """ INFO_SIZE = 300 def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title('agar.io') window.set_default_size(self.win_size.x, self.win_size.y) window.connect('delete-event', Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.POINTER_MOTION_MASK) window.connect('key-press-event', self.key_pressed) window.connect('motion-notify-event', self.mouse_moved) window.connect('button-press-event', self.mouse_pressed) self.drawing_area.connect('draw', self.draw) window.show_all() def focus_player(self, player): """Follow this client regarding center and zoom.""" self.player = player self.world = player.world def show_full_world(self, world=None): """ Show the full world view instead of one client. :param world: optionally update the drawn world """ self.player = None if world: self.world = world def key_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return val = event.keyval try: char = chr(val) except ValueError: char = '' self.input_subscriber.on_key_pressed(val=val, char=char) def mouse_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.mouse_pos = Vec(event.x, event.y) pos_world = self.screen_to_world_pos(self.mouse_pos) self.input_subscriber.on_mouse_moved(pos=self.mouse_pos, pos_world=pos_world) def mouse_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.input_subscriber.on_mouse_pressed(button=event.button) def world_to_screen_pos(self, world_pos): return (world_pos - self.world_center) \ .imul(self.screen_scale).iadd(self.screen_center) def screen_to_world_pos(self, screen_pos): return (screen_pos - self.screen_center) \ .idiv(self.screen_scale).iadd(self.world_center) def world_to_screen_size(self, world_size): return world_size * self.screen_scale def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) self.screen_scale = self.player.scale * window_scale self.world_center = self.player.center self.world = self.player.world elif self.world.size: self.screen_scale = min(self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = 1 self.world_center = Vec(0, 0) def draw(self, widget, cairo_context): c = Canvas(cairo_context) if self.draw_subscriber: self.recalculate() self.draw_subscriber.on_draw_background(c, self) self.draw_subscriber.on_draw_cells(c, self) self.draw_subscriber.on_draw_hud(c, self)
class WorldViewer(object): """ Draws one world and handles keys/mouse. Does not poll for events itself. Calls input_subscriber.on_{key_pressed|mouse_moved}() methods on key/mouse input. Calls draw_subscriber.on_draw_{background|cells|hud}() methods when drawing. """ INFO_SIZE = 300 def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title("agar.io") window.set_default_size(self.win_size.x, self.win_size.y) window.connect("delete-event", Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.POINTER_MOTION_MASK) window.connect("key-press-event", self.key_pressed) window.connect("motion-notify-event", self.mouse_moved) window.connect("button-press-event", self.mouse_pressed) self.drawing_area.connect("draw", self.draw) window.show_all() def focus_player(self, player): """Follow this client regarding center and zoom.""" self.player = player self.world = player.world def show_full_world(self, world=None): """ Show the full world view instead of one client. :param world: optionally update the drawn world """ self.player = None if world: self.world = world def key_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return val = event.keyval try: char = chr(val) except ValueError: char = "" self.input_subscriber.on_key_pressed(val=val, char=char) def mouse_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.mouse_pos = Vec(event.x, event.y) pos_world = self.screen_to_world_pos(self.mouse_pos) self.input_subscriber.on_mouse_moved(pos=self.mouse_pos, pos_world=pos_world) def mouse_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.input_subscriber.on_mouse_pressed(button=event.button) def world_to_screen_pos(self, world_pos): return (world_pos - self.world_center).imul(self.screen_scale).iadd(self.screen_center) def screen_to_world_pos(self, screen_pos): return (screen_pos - self.screen_center).idiv(self.screen_scale).iadd(self.world_center) def world_to_screen_size(self, world_size): return world_size * self.screen_scale def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) self.screen_scale = self.player.scale * window_scale self.world_center = self.player.center self.world = self.player.world elif self.world.size: self.screen_scale = min(self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = 1 self.world_center = Vec(0, 0) def draw(self, _, c): if self.draw_subscriber: self.recalculate() self.draw_subscriber.on_draw_background(c, self) self.draw_subscriber.on_draw_cells(c, self) self.draw_subscriber.on_draw_hud(c, self)
class WorldViewer(object): """ Draws one world and handles keys/mouse. Does not poll for events itself. Calls input_subscriber.on_{key_pressed|mouse_moved}() methods on key/mouse input. Calls draw_subscriber.on_draw_{background|cells|hud}() methods when drawing. """ INFO_SIZE = 300 MIN_SCREEN_SCALE = 0.075 MAX_SCREEN_SCALE = 1 def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.button_subscriber = None self.buttons = [] self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.screen_zoom_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title('agar.io') window.set_default_size(self.win_size.x, self.win_size.y) window.connect('delete-event', Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.SCROLL_MASK) window.connect('key-press-event', self.key_pressed) window.connect('motion-notify-event', self.mouse_moved) window.connect('button-press-event', self.mouse_pressed) window.connect('scroll-event', self.mouse_wheel_moved) self.drawing_area.connect('draw', self.draw) window.show_all() #draw_thread = threading.Thread(target=self.draw_loop) #draw_thread.daemon = True #draw_thread.start() def draw_loop(self): while True: self.drawing_area.queue_draw() time.sleep(0.003) def focus_player(self, player): """Follow this client regarding center and zoom.""" self.player = player self.world = player.world def show_full_world(self, world=None): """ Show the full world view instead of one client. :param world: optionally update the drawn world """ self.player = None if world: self.world = world def key_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return val = event.keyval try: char = chr(val) except ValueError: char = '' self.input_subscriber.on_key_pressed(val=val, char=char) def mouse_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.mouse_pos = Vec(event.x, event.y) pos_world = self.screen_to_world_pos(self.mouse_pos) self.input_subscriber.on_mouse_moved(pos=self.mouse_pos, pos_world=pos_world) def mouse_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.input_subscriber.on_mouse_pressed(button=event.button) if event.button == 1: for button in self.buttons: if button.contains_point(self.mouse_pos): self.button_subscriber.on_button_pressed( button, self.mouse_pos) def mouse_wheel_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if event.direction == Gdk.ScrollDirection.UP and self.screen_zoom_scale < self.MAX_SCREEN_SCALE: self.screen_zoom_scale = min(self.screen_zoom_scale * 1.5, self.MAX_SCREEN_SCALE) if event.direction == Gdk.ScrollDirection.DOWN and self.screen_zoom_scale > self.MIN_SCREEN_SCALE: self.screen_zoom_scale = max(self.screen_zoom_scale * 0.75, self.MIN_SCREEN_SCALE) def register_button(self, button): self.buttons.append(button) if button.contains_point(self.mouse_pos): self.button_subscriber.on_button_hover(button, self.mouse_pos) def world_to_screen_pos(self, world_pos): return (world_pos - self.world_center) \ .imul(self.screen_scale).iadd(self.screen_center) def screen_to_world_pos(self, screen_pos): return (screen_pos - self.screen_center) \ .idiv(self.screen_scale).iadd(self.world_center) def world_to_screen_size(self, world_size): return world_size * self.screen_scale def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused # if self.player.is_alive or (self.player.center.x == 0 and self.player.center.y == 0) or not self.player.scale == 1.0: # HACK due to bug: player scale is sometimes wrong (sent by server?) in spectate mode window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) new_screen_scale = self.player.scale * window_scale * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) smoothing_factor = 0.1 if self.player.is_alive: smoothing_factor = 0.3 self.world_center.x = lerp_smoothing(self.world_center.x, self.player.center.x, smoothing_factor, 0.01) self.world_center.y = lerp_smoothing(self.world_center.y, self.player.center.y, smoothing_factor, 0.01) self.world = self.player.world elif self.world.size: new_screen_scale = min( self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = self.screen_zoom_scale self.world_center = Vec(0, 0) def draw(self, widget, cairo_context): self.buttons = [] c = Canvas(cairo_context) if self.draw_subscriber: self.recalculate() self.draw_subscriber.on_draw_background(c, self) self.draw_subscriber.on_draw_cells(c, self) self.draw_minimap_backgound(c, self) self.draw_subscriber.on_draw_minimap(c, self) self.draw_subscriber.on_draw_hud(c, self) def draw_minimap_backgound(self, c, w): if w.world.size: minimap_w = w.win_size.x / 5 minimap_size = Vec(minimap_w, minimap_w) minimap_scale = minimap_size.x / w.world.size.x minimap_offset = w.win_size - minimap_size def world_to_map(world_pos): pos_from_top_left = world_pos - w.world.top_left return minimap_offset + pos_from_top_left * minimap_scale # minimap background c.fill_rect(minimap_offset, size=minimap_size, color=to_rgba(DARK_GRAY, .8)) # outline the area visible in window c.stroke_rect(world_to_map(w.screen_to_world_pos(Vec(0, 0))), world_to_map(w.screen_to_world_pos(w.win_size)), width=1, color=BLACK)
from unittest import TestCase from agarnet.vec import Vec from agarnet.world import Cell, Player, World screen = Vec(1920, 1080) zero = Vec(0, 0) class WindowMock: def __init__(self, tl=Vec(-10, 10), br=Vec(-10, 10)): self.tl = tl self.br = br self.world_center = (br + tl) / 2 self.screen_center = screen / 2 self.screen_scale = 1 # taken from gagar, same as in official client def screen_to_world_pos(self, screen_pos): return (screen_pos - self.screen_center) \ .idiv(self.screen_scale).iadd(self.world_center) class CellTest(TestCase): def test_lt(self): self.assertLess(Cell(1), Cell(2)) self.assertLess(Cell(2, size=10), Cell(1, size=11)) class WorldTest(TestCase): def test_world(self):
def __init__(self, tl=Vec(-10, 10), br=Vec(-10, 10)): self.tl = tl self.br = br self.world_center = (br + tl) / 2 self.screen_center = screen / 2 self.screen_scale = 1
class WorldViewer(object): """ Draws one world and handles keys/mouse. Does not poll for events itself. Calls input_subscriber.on_{key_pressed|mouse_moved}() methods on key/mouse input. Calls draw_subscriber.on_draw_{background|cells|hud}() methods when drawing. """ INFO_SIZE = 300 MIN_SCREEN_SCALE = 0.075 MAX_SCREEN_SCALE = 1 def __init__(self, world): self.world = world self.player = None # the focused player, or None to show full world # the class instance on which to call on_key_pressed and on_mouse_moved self.input_subscriber = None # same for draw_background, draw_cells, draw_hud self.draw_subscriber = None self.button_subscriber = None self.buttons = [] self.win_size = Vec(1000, 1000 * 9 / 16) self.screen_center = self.win_size / 2 self.screen_scale = 1 self.screen_zoom_scale = 1 self.world_center = Vec(0, 0) self.mouse_pos = Vec(0, 0) window = Gtk.Window() window.set_title('agar.io') window.set_default_size(self.win_size.x, self.win_size.y) window.connect('delete-event', Gtk.main_quit) self.drawing_area = Gtk.DrawingArea() window.add(self.drawing_area) window.set_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.SCROLL_MASK) window.connect('key-press-event', self.key_pressed) window.connect('motion-notify-event', self.mouse_moved) window.connect('button-press-event', self.mouse_pressed) window.connect('scroll-event', self.mouse_wheel_moved) self.drawing_area.connect('draw', self.draw) window.show_all() #draw_thread = threading.Thread(target=self.draw_loop) #draw_thread.daemon = True #draw_thread.start() def draw_loop(self): while True: self.drawing_area.queue_draw() time.sleep(0.003) def focus_player(self, player): """Follow this client regarding center and zoom.""" self.player = player self.world = player.world def show_full_world(self, world=None): """ Show the full world view instead of one client. :param world: optionally update the drawn world """ self.player = None if world: self.world = world def key_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return val = event.keyval try: char = chr(val) except ValueError: char = '' self.input_subscriber.on_key_pressed(val=val, char=char) def mouse_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.mouse_pos = Vec(event.x, event.y) pos_world = self.screen_to_world_pos(self.mouse_pos) self.input_subscriber.on_mouse_moved(pos=self.mouse_pos, pos_world=pos_world) def mouse_pressed(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if not self.input_subscriber: return self.input_subscriber.on_mouse_pressed(button=event.button) if event.button == 1: for button in self.buttons: if button.contains_point(self.mouse_pos): self.button_subscriber.on_button_pressed(button, self.mouse_pos) def mouse_wheel_moved(self, _, event): """Called by GTK. Set input_subscriber to handle this.""" if event.direction == Gdk.ScrollDirection.UP and self.screen_zoom_scale < self.MAX_SCREEN_SCALE: self.screen_zoom_scale = min(self.screen_zoom_scale * 1.5, self.MAX_SCREEN_SCALE) if event.direction == Gdk.ScrollDirection.DOWN and self.screen_zoom_scale > self.MIN_SCREEN_SCALE: self.screen_zoom_scale = max(self.screen_zoom_scale * 0.75, self.MIN_SCREEN_SCALE) def register_button(self, button): self.buttons.append(button) if button.contains_point(self.mouse_pos): self.button_subscriber.on_button_hover(button, self.mouse_pos) def world_to_screen_pos(self, world_pos): return (world_pos - self.world_center) \ .imul(self.screen_scale).iadd(self.screen_center) def screen_to_world_pos(self, screen_pos): return (screen_pos - self.screen_center) \ .idiv(self.screen_scale).iadd(self.world_center) def world_to_screen_size(self, world_size): return world_size * self.screen_scale def recalculate(self): alloc = self.drawing_area.get_allocation() self.win_size.set(alloc.width, alloc.height) self.screen_center = self.win_size / 2 if self.player: # any client is focused # if self.player.is_alive or (self.player.center.x == 0 and self.player.center.y == 0) or not self.player.scale == 1.0: # HACK due to bug: player scale is sometimes wrong (sent by server?) in spectate mode window_scale = max(self.win_size.x / 1920, self.win_size.y / 1080) new_screen_scale = self.player.scale * window_scale * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) smoothing_factor = 0.1 if self.player.is_alive: smoothing_factor = 0.3 self.world_center.x = lerp_smoothing(self.world_center.x, self.player.center.x, smoothing_factor, 0.01) self.world_center.y = lerp_smoothing(self.world_center.y, self.player.center.y, smoothing_factor, 0.01) self.world = self.player.world elif self.world.size: new_screen_scale = min(self.win_size.x / self.world.size.x, self.win_size.y / self.world.size.y) * self.screen_zoom_scale self.screen_scale = lerp_smoothing(self.screen_scale, new_screen_scale, 0.1, 0.0001) self.world_center = self.world.center else: # happens when the window gets drawn before the world got updated self.screen_scale = self.screen_zoom_scale self.world_center = Vec(0, 0) def draw(self, widget, cairo_context): self.buttons = [] c = Canvas(cairo_context) if self.draw_subscriber: self.recalculate() self.draw_subscriber.on_draw_background(c, self) self.draw_subscriber.on_draw_cells(c, self) self.draw_minimap_backgound(c, self) self.draw_subscriber.on_draw_minimap(c, self) self.draw_subscriber.on_draw_hud(c, self) def draw_minimap_backgound(self, c, w): if w.world.size: minimap_w = w.win_size.x / 5 minimap_size = Vec(minimap_w, minimap_w) minimap_scale = minimap_size.x / w.world.size.x minimap_offset = w.win_size - minimap_size def world_to_map(world_pos): pos_from_top_left = world_pos - w.world.top_left return minimap_offset + pos_from_top_left * minimap_scale # minimap background c.fill_rect(minimap_offset, size=minimap_size, color=to_rgba(DARK_GRAY, .8)) # outline the area visible in window c.stroke_rect(world_to_map(w.screen_to_world_pos(Vec(0, 0))), world_to_map(w.screen_to_world_pos(w.win_size)), width=1, color=BLACK)