mirror of
https://github.com/cyberarm/i-mic-rts.git
synced 2025-12-13 14:52:35 +00:00
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:
@@ -5,6 +5,7 @@ rescue LoadError
|
||||
end
|
||||
|
||||
require_relative "lib/version"
|
||||
require_relative "lib/errors"
|
||||
require_relative "lib/window"
|
||||
require_relative "lib/camera"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
6
lib/errors.rb
Normal file
6
lib/errors.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class IMICRTS
|
||||
class DesyncError < Exception
|
||||
end
|
||||
class UndefinedOrderError < Exception
|
||||
end
|
||||
end
|
||||
26
lib/order.rb
26
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,
|
||||
|
||||
18
lib/orders/camera_move.rb
Normal file
18
lib/orders/camera_move.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
21
lib/orders/deselected_units.rb
Normal file
21
lib/orders/deselected_units.rb
Normal 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
20
lib/orders/move.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
19
lib/orders/stop.rb
Normal file
19
lib/orders/stop.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -8,7 +8,7 @@ class IMICRTS
|
||||
:BUILDING,
|
||||
:AIR_VEHICLE,
|
||||
|
||||
:ENTITY_BOUNDING_BOX,
|
||||
:ENTITY_RADIUS,
|
||||
:ENTITY_GIZMOS, # Health bar and the like
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user