From d6615872ba520813fdda6f674e28818a6a06452a Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 9 Oct 2019 11:32:59 -0500 Subject: [PATCH] Orders are now de/serializable, and scheduleable, Entities now show a circle around themselves when selected and draw a line to their target --- i-mic-rts.rb | 1 + lib/camera.rb | 15 +++++++--- lib/director.rb | 51 ++++++++++++++++++++++++++++++++-- lib/entity.rb | 37 +++++++++++++++++++++--- lib/errors.rb | 6 ++++ lib/order.rb | 26 ++++++++++++----- lib/orders/camera_move.rb | 18 ++++++++++++ lib/orders/camera_moved.rb | 4 --- lib/orders/deselected_units.rb | 21 ++++++++++++++ lib/orders/move.rb | 20 +++++++++++++ lib/orders/selected_units.rb | 20 +++++++++++-- lib/orders/stop.rb | 19 +++++++++++++ lib/player.rb | 21 ++++++++++++-- lib/states/game.rb | 18 +++++++----- lib/zorder.rb | 2 +- 15 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 lib/errors.rb create mode 100644 lib/orders/camera_move.rb delete mode 100644 lib/orders/camera_moved.rb create mode 100644 lib/orders/deselected_units.rb create mode 100644 lib/orders/move.rb create mode 100644 lib/orders/stop.rb diff --git a/i-mic-rts.rb b/i-mic-rts.rb index 8969202..3914365 100644 --- a/i-mic-rts.rb +++ b/i-mic-rts.rb @@ -5,6 +5,7 @@ rescue LoadError end require_relative "lib/version" +require_relative "lib/errors" require_relative "lib/window" require_relative "lib/camera" diff --git a/lib/camera.rb b/lib/camera.rb index 2883d4b..4549423 100644 --- a/lib/camera.rb +++ b/lib/camera.rb @@ -8,8 +8,8 @@ class IMICRTS @velocity = CyberarmEngine::Vector.new(0.0, 0.0) @zoom = 1.0 - @min_zoom = 0.25 - @max_zoom = 4.0 + @min_zoom = 0.50 + @max_zoom = 5.0 @drag = 0.8 # Used to arrest velocity @grab_drag = 0.5 # Used when camera is panned using middle mouse button @@ -22,8 +22,6 @@ class IMICRTS Gosu.clip_to(@viewport.min.x, @viewport.min.y, @viewport.max.x, @viewport.max.y) do Gosu.transform(*worldspace.elements) do block.call - - Gosu.draw_line(@drag_start.x, @drag_start.y, Gosu::Color::RED, window.mouse.x, window.mouse_x, Gosu::Color::RED, Float::INFINITY) if @drag_start end end end @@ -98,6 +96,11 @@ class IMICRTS end end + def move_to(x, y, zoom) + @position.x, @position.y = x, y + @zoom = zoom + end + def button_down(id) case id when Gosu::KB_H @@ -118,5 +121,9 @@ class IMICRTS @drag_start = nil end end + + def to_a + [@position.x, @position.y, @zoom] + end end end \ No newline at end of file diff --git a/lib/director.rb b/lib/director.rb index 47f8ae8..f1abb96 100644 --- a/lib/director.rb +++ b/lib/director.rb @@ -20,10 +20,35 @@ class IMICRTS tick @connection.update end + + @players.each { |player| player.update } end def tick - @players.each { |player| player.tick(@current_tick) } + @players.each do |player| + player.tick(@current_tick) + + # Records where player is looking at tick + # record_order(Order::CAMERA_MOVE, player.id, *player.camera.to_a)# if player.camera_moved? + + player.orders.sort_by {|order| order.tick_id }.each do |order| + raise DesyncError, "Have orders from an already processed tick! (#{order.tick_id} < #{current_tick})" if order.tick_id < @current_tick + + if _order = Order.get(Integer(order.serialized_order.unpack("C").first)) + # Chop off Order ID + _order_data = order.serialized_order + _order_args = _order.deserialize(_order_data[1.._order_data.length - 1], self) + + execute_order(_order.id, *_order_args) + + player.orders.delete(order) + else + raise UndefinedOrderError + end + + break if order.tick_id > @current_tick + end + end @current_tick += 1 end @@ -32,7 +57,29 @@ class IMICRTS @players.find { |player| player.id == id } end - def issue_order(order_id, *args) + def record_order(order_id, *args) + if order = Order.get(order_id) + struct = order.struct(args) + + player(struct.player_id).orders.push(Player::ScheduledOrder.new( order_id, @current_tick + 1, order.serialize(struct, self) )) + else + raise "Undefined order: #{Order.order_name(order_id)}" + end + end + + def schedule_order(order_id, *args) + if order = Order.get(order_id) + struct = order.struct(args) + + pp Order.order_name(order_id) + + player(struct.player_id).orders.push(Player::ScheduledOrder.new( order_id, @current_tick + 2, order.serialize(struct, self) )) + else + raise "Undefined order: #{Order.order_name(order_id)}" + end + end + + def execute_order(order_id, *args) if order = Order.get(order_id) order.execute(self, *args) else diff --git a/lib/entity.rb b/lib/entity.rb index d85fe1a..d0f0db1 100644 --- a/lib/entity.rb +++ b/lib/entity.rb @@ -1,7 +1,8 @@ class IMICRTS class Entity - attr_reader :id, :position, :angle, :radius - def initialize(id:, manifest: nil, images:, position:, angle:) + attr_reader :player, :id, :position, :angle, :radius, :target, :state + def initialize(player:, id:, manifest: nil, images:, position:, angle:) + @player = player @id = id @manifest = manifest @images = images @@ -9,6 +10,23 @@ class IMICRTS @angle = angle @radius = 32 / 2 + @target = nil + @state = :idle + + # process_manifest + + @goal_color = Gosu::Color.argb(175, 25, 200, 25) + @target_color = Gosu::Color.argb(175, 200, 25, 25) + end + + def serialize + end + + def deserialize + end + + def target=(entity) + @target = entity end def hit?(x_or_vector, y = nil) @@ -27,16 +45,27 @@ class IMICRTS @images.draw_rot(@position.x, @position.y, @position.z, @angle) end + def update + rotate_towards(@target) if @target + end + def selected_draw - draw_bounding_box + draw_radius draw_gizmos end - def draw_bounding_box + def draw_radius + Gosu.draw_circle(@position.x, @position.y, @radius, ZOrder::ENTITY_RADIUS, @player.color) end def draw_gizmos Gosu.draw_rect(@position.x - @radius, @position.y - (@radius + 2), @radius * 2, 2, Gosu::Color::GREEN, ZOrder::ENTITY_GIZMOS) + + if @target.is_a?(IMICRTS::Entity) + Gosu.draw_line(@position.x, @position.y, @target_color, @target.position.x, @target.position.y, @target_color, ZOrder::ENTITY_GIZMOS) if @target + else + Gosu.draw_line(@position.x, @position.y, @goal_color, @target.x, @target.y, @goal_color, ZOrder::ENTITY_GIZMOS) if @target + end end def rotate_towards(vector) diff --git a/lib/errors.rb b/lib/errors.rb new file mode 100644 index 0000000..b48438e --- /dev/null +++ b/lib/errors.rb @@ -0,0 +1,6 @@ +class IMICRTS + class DesyncError < Exception + end + class UndefinedOrderError < Exception + end +end \ No newline at end of file diff --git a/lib/order.rb b/lib/order.rb index d308c99..82e0036 100644 --- a/lib/order.rb +++ b/lib/order.rb @@ -16,7 +16,19 @@ class IMICRTS @@orders[order_id] = IMICRTS::Order.new(id: order_id, arguments: arguments, &handler) end - attr_reader :id + def self.define_serializer(order_id, &handler) + get(order_id).define_singleton_method(:serialize) do |order, director| + handler.call(order, director) + end + end + + def self.define_deserializer(order_id, &handler) + get(order_id).define_singleton_method(:deserialize) do |string, director| + handler.call(string, director) + end + end + + attr_reader :id, :arguments def initialize(id:, arguments:, &handler) @id = id @arguments = arguments @@ -24,10 +36,11 @@ class IMICRTS end def execute(director, *arguments) - @handler.call(arguments(arguments), director) + pp Order.order_name(self.id) + @handler.call(struct(arguments), director) end - def arguments(args) + def struct(args) raise "Did not receive correct number of arguments: got #{args.size} expected #{@arguments.size}." unless @arguments.size == args.size hash = FriendlyHash.new @@ -38,16 +51,15 @@ class IMICRTS return hash end - def serialize + def serialize(order, director) end - def deserialize + def deserialize(string, director) end end orders = [ - :CAMERA_MOVED, - :CAMERA_ZOOMED, + :CAMERA_MOVE, :ENTITY_SELECTED, :ENTITY_DESELECTED, diff --git a/lib/orders/camera_move.rb b/lib/orders/camera_move.rb new file mode 100644 index 0000000..ce1b43d --- /dev/null +++ b/lib/orders/camera_move.rb @@ -0,0 +1,18 @@ +IMICRTS::Order.define_handler(IMICRTS::Order::CAMERA_MOVE, arguments: [:player_id, :x, :y, :zoom]) do |order, director| + director.player(order.player_id).camera.move_to(order.x, order.y, order.zoom) +end + +IMICRTS::Order.define_serializer(IMICRTS::Order::CAMERA_MOVE) do |order, director| + # Order ID: char as C + # Player ID: char as C + # Position X/Y: Double as G + # Zoom: Double as G + + [IMICRTS::Order::CAMERA_MOVE, order.player_id, order.x, order.y, order.zoom].pack("CCGGG") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::CAMERA_MOVE) do |string, director| + # Player ID | Camera X | Camera Y | Camera Zoom + # char | double | double | double + string.unpack("CGGG") +end diff --git a/lib/orders/camera_moved.rb b/lib/orders/camera_moved.rb deleted file mode 100644 index 385e174..0000000 --- a/lib/orders/camera_moved.rb +++ /dev/null @@ -1,4 +0,0 @@ -IMICRTS::Order.define_handler(IMICRTS::Order::CAMERA_MOVED, arguments: [:player_id, :x, :y]) do |order, director| - director.player(order.player_id).move_camera(order.x, order.y) -end - diff --git a/lib/orders/deselected_units.rb b/lib/orders/deselected_units.rb new file mode 100644 index 0000000..fbf6f7f --- /dev/null +++ b/lib/orders/deselected_units.rb @@ -0,0 +1,21 @@ +IMICRTS::Order.define_handler(IMICRTS::Order::DESELECTED_UNITS, arguments: [:player_id, :entities]) do |order, director| + selected_entities = director.player(order.player_id).selected_entities + selected_entities.select { |ent| order.entities.include?(ent)}.each { |ent| selected_entities.delete(ent) } +end + +IMICRTS::Order.define_serializer(IMICRTS::Order::DESELECTED_UNITS) do |order, director| + # Order ID | Player ID | Entity IDs + # char | char | integers + + [IMICRTS::Order::DESELECTED_UNITS, order.player_id].pack("CC") + order.entities.map { |ent| ent.id }.pack("N*") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::DESELECTED_UNITS) do |string, director| + # String fed into deserializer has Order ID removed + # Player ID | Entity IDs + # char | integers + player_id = string.unpack("C").first + entities = string[1..string.length - 1].unpack("N*").map { |ent_id| director.player(player_id).entity(ent_id) } + + [player_id, entities] +end \ No newline at end of file diff --git a/lib/orders/move.rb b/lib/orders/move.rb new file mode 100644 index 0000000..61570df --- /dev/null +++ b/lib/orders/move.rb @@ -0,0 +1,20 @@ +IMICRTS::Order.define_handler(IMICRTS::Order::MOVE, arguments: [:player_id, :vector]) do |order, director| + director.player(order.player_id).selected_entities.each do |entity| + entity.target = order.vector + end +end + +IMICRTS::Order.define_serializer(IMICRTS::Order::MOVE) do |order, director| + # Order ID | Player ID | Target X | Target Y + # char | char | double | double + + [IMICRTS::Order::MOVE, order.player_id, order.vector.x, order.vector.y].pack("CCGG") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::MOVE) do |string, director| + # String fed into deserializer has Order ID removed + # Player ID | Target X | Target Y + # char | double | double + data = string.unpack("CGG") + [data[0], CyberarmEngine::Vector.new(data[1], data[2])] +end diff --git a/lib/orders/selected_units.rb b/lib/orders/selected_units.rb index 08c3aae..92644df 100644 --- a/lib/orders/selected_units.rb +++ b/lib/orders/selected_units.rb @@ -1,5 +1,21 @@ -IMICRTS::Order.define_handler(IMICRTS::Order::SELECTED_UNITS, arguments: [:player_id, :ids]) do |order, director| +IMICRTS::Order.define_handler(IMICRTS::Order::SELECTED_UNITS, arguments: [:player_id, :entities]) do |order, director| director.player(order.player_id).selected_entities.clear - director.player(order.player_id).selected_entities.push(*order.ids) + director.player(order.player_id).selected_entities.push(*order.entities) end +IMICRTS::Order.define_serializer(IMICRTS::Order::SELECTED_UNITS) do |order, director| + # Order ID | Player ID | Entity IDs + # char | char | integers + + [IMICRTS::Order::SELECTED_UNITS, order.player_id].pack("CC") + order.entities.map { |ent| ent.id }.pack("N*") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::SELECTED_UNITS) do |string, director| + # String fed into deserializer has Order ID removed + # Player ID | Entity IDs + # char | integers + player_id = string.unpack("C").first + entities = string[1..string.length - 1].unpack("N*").map { |ent_id| director.player(player_id).entity(ent_id) } + + [player_id, entities] +end diff --git a/lib/orders/stop.rb b/lib/orders/stop.rb new file mode 100644 index 0000000..656beed --- /dev/null +++ b/lib/orders/stop.rb @@ -0,0 +1,19 @@ +IMICRTS::Order.define_handler(IMICRTS::Order::STOP, arguments: [:player_id]) do |order, director| + director.player(order.player_id).selected_entities.each do |entity| + entity.target = nil + end +end + +IMICRTS::Order.define_serializer(IMICRTS::Order::STOP) do |order, director| + # Order ID | Player ID + # char | char + + [IMICRTS::Order::STOP, order.player_id].pack("CC") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::STOP) do |string, director| + # String fed into deserializer has Order ID removed + # Player ID + # char + string.unpack("C") +end diff --git a/lib/player.rb b/lib/player.rb index 142f487..5ea412b 100644 --- a/lib/player.rb +++ b/lib/player.rb @@ -1,10 +1,12 @@ class IMICRTS class Player - attr_reader :id, :name, :entities, :orders, :camera + attr_reader :id, :name, :color, :team, :entities, :orders, :camera attr_reader :selected_entities - def initialize(id:, name: nil) + def initialize(id:, name: nil, color: Gosu::Color.rgb(rand(150..200), rand(100..200), rand(150..200)), team: nil) @id = id @name = name ? name : "Novice-#{id}" + @color = color + @team = team @entities = [] @orders = [] @@ -17,6 +19,10 @@ class IMICRTS def tick(tick_id) end + def update + @entities.each(&:update) + end + def entity(id) @entities.find { |ent| ent.id == id } end @@ -24,5 +30,16 @@ class IMICRTS def next_entity_id @current_entity_id += 1 end + + class ScheduledOrder + attr_reader :tick_id, :serialized_order + def initialize(order_id, tick_id, serialized_order) + @order_id = order_id + @tick_id, @serialized_order = tick_id, serialized_order + + raise ArgumentError, "Tick ID is nil!" unless @tick_id + raise ArgumentError, "Serialized order for #{Order.order_name(order_id)} is nil!" unless @serialized_order + end + end end end \ No newline at end of file diff --git a/lib/states/game.rb b/lib/states/game.rb index 032284d..922f2e5 100644 --- a/lib/states/game.rb +++ b/lib/states/game.rb @@ -19,6 +19,7 @@ class IMICRTS @buttons = stack(width: 75) do @h = button("Harvester", width: 1.0) do @player.entities << Entity.new( + player: @player, id: @player.next_entity_id, images: Gosu::Image.new("#{ASSETS_PATH}/vehicles/harvester/images/harvester.png", retro: true), position: CyberarmEngine::Vector.new(rand(window.width), rand(window.height), ZOrder::GROUND_VEHICLE), @@ -27,6 +28,7 @@ class IMICRTS end @c = button("Construction Worker", width: 1.0) do @player.entities << Entity.new( + player: @player, id: @player.next_entity_id, images: Gosu::Image.new("#{ASSETS_PATH}/vehicles/construction_worker/images/construction_worker.png", retro: true), position: CyberarmEngine::Vector.new(rand(window.width), rand(window.height), ZOrder::GROUND_VEHICLE), @@ -81,10 +83,6 @@ class IMICRTS @director.update @player.camera.update - @player.selected_entities.each do |ent| - ent.rotate_towards(@goal) if @goal - end - if @selection_start select_entities end @@ -113,13 +111,17 @@ class IMICRTS super case id + when Gosu::KB_S + @director.schedule_order(Order::STOP, @player.id) + when Gosu::MS_LEFT unless @sidebar.hit?(window.mouse_x, window.mouse_y) @selection_start = @player.camera.transform(window.mouse) end when Gosu::MS_RIGHT - @anchor = nil + @director.schedule_order(Order::MOVE, @player.id, @player.camera.transform(window.mouse)) end + @player.camera.button_down(id) unless @sidebar.hit?(window.mouse_x, window.mouse_y) end @@ -128,12 +130,14 @@ class IMICRTS case id when Gosu::MS_RIGHT - @goal = @player.camera.transform(window.mouse) when Gosu::MS_LEFT @box = nil @selection_start = nil - @director.issue_order(Order::SELECTED_UNITS, @player.id, @selected_entities) + diff = (@player.selected_entities - @selected_entities) + + @director.schedule_order(Order::DESELECTED_UNITS, @player.id, diff) + @director.schedule_order(Order::SELECTED_UNITS, @player.id, @selected_entities) end @player.camera.button_up(id) diff --git a/lib/zorder.rb b/lib/zorder.rb index 7f7d2a2..71066e2 100644 --- a/lib/zorder.rb +++ b/lib/zorder.rb @@ -8,7 +8,7 @@ class IMICRTS :BUILDING, :AIR_VEHICLE, - :ENTITY_BOUNDING_BOX, + :ENTITY_RADIUS, :ENTITY_GIZMOS, # Health bar and the like ]