Orders are now de/serializable, and scheduleable, Entities now show a circle around themselves when selected and draw a line to their target

This commit is contained in:
2019-10-09 11:32:59 -05:00
parent 2a179ed935
commit d6615872ba
15 changed files with 246 additions and 33 deletions

View File

@@ -5,6 +5,7 @@ rescue LoadError
end end
require_relative "lib/version" require_relative "lib/version"
require_relative "lib/errors"
require_relative "lib/window" require_relative "lib/window"
require_relative "lib/camera" require_relative "lib/camera"

View File

@@ -8,8 +8,8 @@ class IMICRTS
@velocity = CyberarmEngine::Vector.new(0.0, 0.0) @velocity = CyberarmEngine::Vector.new(0.0, 0.0)
@zoom = 1.0 @zoom = 1.0
@min_zoom = 0.25 @min_zoom = 0.50
@max_zoom = 4.0 @max_zoom = 5.0
@drag = 0.8 # Used to arrest velocity @drag = 0.8 # Used to arrest velocity
@grab_drag = 0.5 # Used when camera is panned using middle mouse button @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.clip_to(@viewport.min.x, @viewport.min.y, @viewport.max.x, @viewport.max.y) do
Gosu.transform(*worldspace.elements) do Gosu.transform(*worldspace.elements) do
block.call 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 end
end end
@@ -98,6 +96,11 @@ class IMICRTS
end end
end end
def move_to(x, y, zoom)
@position.x, @position.y = x, y
@zoom = zoom
end
def button_down(id) def button_down(id)
case id case id
when Gosu::KB_H when Gosu::KB_H
@@ -118,5 +121,9 @@ class IMICRTS
@drag_start = nil @drag_start = nil
end end
end end
def to_a
[@position.x, @position.y, @zoom]
end
end end
end end

View File

@@ -20,10 +20,35 @@ class IMICRTS
tick tick
@connection.update @connection.update
end end
@players.each { |player| player.update }
end end
def tick 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 @current_tick += 1
end end
@@ -32,7 +57,29 @@ class IMICRTS
@players.find { |player| player.id == id } @players.find { |player| player.id == id }
end 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) if order = Order.get(order_id)
order.execute(self, *args) order.execute(self, *args)
else else

View File

@@ -1,7 +1,8 @@
class IMICRTS class IMICRTS
class Entity class Entity
attr_reader :id, :position, :angle, :radius attr_reader :player, :id, :position, :angle, :radius, :target, :state
def initialize(id:, manifest: nil, images:, position:, angle:) def initialize(player:, id:, manifest: nil, images:, position:, angle:)
@player = player
@id = id @id = id
@manifest = manifest @manifest = manifest
@images = images @images = images
@@ -9,6 +10,23 @@ class IMICRTS
@angle = angle @angle = angle
@radius = 32 / 2 @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 end
def hit?(x_or_vector, y = nil) def hit?(x_or_vector, y = nil)
@@ -27,16 +45,27 @@ class IMICRTS
@images.draw_rot(@position.x, @position.y, @position.z, @angle) @images.draw_rot(@position.x, @position.y, @position.z, @angle)
end end
def update
rotate_towards(@target) if @target
end
def selected_draw def selected_draw
draw_bounding_box draw_radius
draw_gizmos draw_gizmos
end end
def draw_bounding_box def draw_radius
Gosu.draw_circle(@position.x, @position.y, @radius, ZOrder::ENTITY_RADIUS, @player.color)
end end
def draw_gizmos def draw_gizmos
Gosu.draw_rect(@position.x - @radius, @position.y - (@radius + 2), @radius * 2, 2, Gosu::Color::GREEN, ZOrder::ENTITY_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 end
def rotate_towards(vector) def rotate_towards(vector)

6
lib/errors.rb Normal file
View File

@@ -0,0 +1,6 @@
class IMICRTS
class DesyncError < Exception
end
class UndefinedOrderError < Exception
end
end

View File

@@ -16,7 +16,19 @@ class IMICRTS
@@orders[order_id] = IMICRTS::Order.new(id: order_id, arguments: arguments, &handler) @@orders[order_id] = IMICRTS::Order.new(id: order_id, arguments: arguments, &handler)
end 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) def initialize(id:, arguments:, &handler)
@id = id @id = id
@arguments = arguments @arguments = arguments
@@ -24,10 +36,11 @@ class IMICRTS
end end
def execute(director, *arguments) def execute(director, *arguments)
@handler.call(arguments(arguments), director) pp Order.order_name(self.id)
@handler.call(struct(arguments), director)
end 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 raise "Did not receive correct number of arguments: got #{args.size} expected #{@arguments.size}." unless @arguments.size == args.size
hash = FriendlyHash.new hash = FriendlyHash.new
@@ -38,16 +51,15 @@ class IMICRTS
return hash return hash
end end
def serialize def serialize(order, director)
end end
def deserialize def deserialize(string, director)
end end
end end
orders = [ orders = [
:CAMERA_MOVED, :CAMERA_MOVE,
:CAMERA_ZOOMED,
:ENTITY_SELECTED, :ENTITY_SELECTED,
:ENTITY_DESELECTED, :ENTITY_DESELECTED,

18
lib/orders/camera_move.rb Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

20
lib/orders/move.rb Normal file
View File

@@ -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

View File

@@ -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.clear
director.player(order.player_id).selected_entities.push(*order.ids) director.player(order.player_id).selected_entities.push(*order.entities)
end 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

19
lib/orders/stop.rb Normal file
View File

@@ -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

View File

@@ -1,10 +1,12 @@
class IMICRTS class IMICRTS
class Player class Player
attr_reader :id, :name, :entities, :orders, :camera attr_reader :id, :name, :color, :team, :entities, :orders, :camera
attr_reader :selected_entities 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 @id = id
@name = name ? name : "Novice-#{id}" @name = name ? name : "Novice-#{id}"
@color = color
@team = team
@entities = [] @entities = []
@orders = [] @orders = []
@@ -17,6 +19,10 @@ class IMICRTS
def tick(tick_id) def tick(tick_id)
end end
def update
@entities.each(&:update)
end
def entity(id) def entity(id)
@entities.find { |ent| ent.id == id } @entities.find { |ent| ent.id == id }
end end
@@ -24,5 +30,16 @@ class IMICRTS
def next_entity_id def next_entity_id
@current_entity_id += 1 @current_entity_id += 1
end 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
end end

View File

@@ -19,6 +19,7 @@ class IMICRTS
@buttons = stack(width: 75) do @buttons = stack(width: 75) do
@h = button("Harvester", width: 1.0) do @h = button("Harvester", width: 1.0) do
@player.entities << Entity.new( @player.entities << Entity.new(
player: @player,
id: @player.next_entity_id, id: @player.next_entity_id,
images: Gosu::Image.new("#{ASSETS_PATH}/vehicles/harvester/images/harvester.png", retro: true), 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), position: CyberarmEngine::Vector.new(rand(window.width), rand(window.height), ZOrder::GROUND_VEHICLE),
@@ -27,6 +28,7 @@ class IMICRTS
end end
@c = button("Construction Worker", width: 1.0) do @c = button("Construction Worker", width: 1.0) do
@player.entities << Entity.new( @player.entities << Entity.new(
player: @player,
id: @player.next_entity_id, id: @player.next_entity_id,
images: Gosu::Image.new("#{ASSETS_PATH}/vehicles/construction_worker/images/construction_worker.png", retro: true), 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), position: CyberarmEngine::Vector.new(rand(window.width), rand(window.height), ZOrder::GROUND_VEHICLE),
@@ -81,10 +83,6 @@ class IMICRTS
@director.update @director.update
@player.camera.update @player.camera.update
@player.selected_entities.each do |ent|
ent.rotate_towards(@goal) if @goal
end
if @selection_start if @selection_start
select_entities select_entities
end end
@@ -113,13 +111,17 @@ class IMICRTS
super super
case id case id
when Gosu::KB_S
@director.schedule_order(Order::STOP, @player.id)
when Gosu::MS_LEFT when Gosu::MS_LEFT
unless @sidebar.hit?(window.mouse_x, window.mouse_y) unless @sidebar.hit?(window.mouse_x, window.mouse_y)
@selection_start = @player.camera.transform(window.mouse) @selection_start = @player.camera.transform(window.mouse)
end end
when Gosu::MS_RIGHT when Gosu::MS_RIGHT
@anchor = nil @director.schedule_order(Order::MOVE, @player.id, @player.camera.transform(window.mouse))
end end
@player.camera.button_down(id) unless @sidebar.hit?(window.mouse_x, window.mouse_y) @player.camera.button_down(id) unless @sidebar.hit?(window.mouse_x, window.mouse_y)
end end
@@ -128,12 +130,14 @@ class IMICRTS
case id case id
when Gosu::MS_RIGHT when Gosu::MS_RIGHT
@goal = @player.camera.transform(window.mouse)
when Gosu::MS_LEFT when Gosu::MS_LEFT
@box = nil @box = nil
@selection_start = 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 end
@player.camera.button_up(id) @player.camera.button_up(id)

View File

@@ -8,7 +8,7 @@ class IMICRTS
:BUILDING, :BUILDING,
:AIR_VEHICLE, :AIR_VEHICLE,
:ENTITY_BOUNDING_BOX, :ENTITY_RADIUS,
:ENTITY_GIZMOS, # Health bar and the like :ENTITY_GIZMOS, # Health bar and the like
] ]