added on_order handler to entities and components, added visibility map

This commit is contained in:
2021-01-15 17:41:06 -06:00
parent e30d73d4d7
commit 19c5fc8d73
22 changed files with 164 additions and 51 deletions

View File

@@ -39,12 +39,14 @@ require_relative "lib/particle_emitter"
require_relative "lib/entity"
require_relative "lib/map"
require_relative "lib/tiled_map"
require_relative "lib/visiblity_map"
require_relative "lib/pathfinder"
require_relative "lib/order"
require_relative "lib/friendly_hash"
require_relative "lib/director"
require_relative "lib/player"
require_relative "lib/ai/ai_player"
require_relative "lib/tool"
require_relative "lib/connection"

15
lib/ai/ai_player.rb Normal file
View File

@@ -0,0 +1,15 @@
class IMICRTS
class AIPlayer < Player
def tick(tick_id)
super
think
end
def think
# build base
# construct army
# attack
end
end
end

View File

@@ -1,6 +1,7 @@
class IMICRTS
class Camera
attr_reader :viewport, :position, :velocity, :zoom, :drag
def initialize(viewport:, scroll_speed: 10, position: CyberarmEngine::Vector.new(0.0, 0.0))
@viewport = CyberarmEngine::BoundingBox.new(viewport[0], viewport[1], viewport[2], viewport[3])
@scroll_speed = scroll_speed
@@ -15,7 +16,9 @@ class IMICRTS
@grab_drag = 0.5 # Used when camera is panned using middle mouse button
end
def window; $window; end
def window;
$window;
end
def draw(&block)
if block

View File

@@ -44,6 +44,9 @@ class IMICRTS
def tick(tick_id)
end
def on_order(type, order)
end
end
end

View File

@@ -17,6 +17,16 @@ class IMICRTS
end
end
def on_order(type, order)
case type
when IMICRTS::Order::MOVE
@parent.target = order.vector
when IMICRTS::Order::STOP
@parent.target = nil
@pathfinder = nil
end
end
def rotate_towards(vector)
angle = Gosu.angle(@parent.position.x, @parent.position.y, vector.x, vector.y)
a = (360.0 + (angle - @parent.angle)) % 360.0
@@ -34,7 +44,7 @@ class IMICRTS
end
def follow_path
if @pathfinder && node = @pathfinder.path_current_node
if @pathfinder && (node = @pathfinder.path_current_node)
@pathfinder.path_next_node if @pathfinder.at_current_path_node?(@parent)
@parent.position -= (@parent.position.xy - (node.tile.position + @parent.director.map.tile_size / 2).xy).normalized * @parent.speed
end

View File

@@ -1,5 +1,12 @@
class IMICRTS
class Spawner < Component
attr_accessor :spawnpoint
def setup
@spawnpoint = @parent.position.clone
@spawnpoint.y += 64
end
def tick(tick_id)
item = @parent.component(:build_queue).queue.first
@@ -12,5 +19,15 @@ class IMICRTS
item.completed = true
@parent.director.schedule_order(IMICRTS::Order::BUILD_UNIT_COMPLETE, @parent.player.id, @parent.id)
end
def on_order(type, order)
case type
when IMICRTS::Order::BUILD_UNIT_COMPLETE
item = @parent.component(:build_queue).queue.shift
ent = @parent.director.spawn_entity(player_id: @parent.player.id, name: item.entity.name, position: @spawnpoint)
ent.target = @parent.component(:waypoint).waypoint if @parent.component(:waypoint)
end
end
end
end

View File

@@ -33,6 +33,13 @@ class IMICRTS
end
end
def on_order(type, order)
case type
when IMICRTS::Order::CONSTRUCTION_COMPLETE
data.construction_complete = true
end
end
def construction_complete?
data.construction_progress >= data.construction_goal && data.construction_complete
end

View File

@@ -2,7 +2,7 @@ class IMICRTS
class Waypoint < Component
def setup
@waypoint = @parent.position.clone
@waypoint.y += @parent.director.map.tile_size
@waypoint.y += @parent.director.map.tile_size * 2
@waypoint_color = 0xffffff00
end

View File

@@ -13,6 +13,8 @@ IMICRTS::Entity.define_entity(:helipad, :structure, 1_000, 100, "Builds and rear
entity.has(:spawner)
entity.has(:build_queue)
entity.has(:sidebar_actions)
entity.component(:spawner).spawnpoint = entity.position.clone
entity.component(:sidebar_actions).add(:add_to_build_queue, { entity: :helicopter })
end

View File

@@ -35,4 +35,15 @@ IMICRTS::Entity.define_entity(:refinery, :structure, 1_400, 200, "Generates cred
end
end
end
entity.on_order do |type, order|
case type
when IMICRTS::Order::CONSTRUCTION_COMPLETE
pos = entity.position.clone
pos.x += 32
pos.y += 64
entity.director.spawn_entity(player_id: entity.player.id, name: :harvester, position: pos)
end
end
end

View File

@@ -6,6 +6,7 @@ IMICRTS::Entity.define_entity(:helicopter, :unit, 400, 40, "Attacks ground targe
entity.radius = 14
entity.movement = :air
entity.max_health = 100.0
entity.position.z = IMICRTS::ZOrder::AIR_VEHICLE
entity.body_image = "vehicles/helicopter/helicopter.png"
entity.shell_image = "vehicles/helicopter/helicopter_shell.png"

View File

@@ -15,7 +15,7 @@ class IMICRTS
end
attr_reader :director, :player, :id, :name, :type, :data, :proto_entity
attr_accessor :position, :angle, :radius, :target, :state, :movement, :health, :max_health,
attr_accessor :position, :angle, :sight_radius, :range_radius, :radius, :target, :state, :movement, :health, :max_health,
:speed, :turret, :center, :scale, :particle_emitters, :color
def initialize(name:, player:, id:, position:, angle:, director:, proto_entity: false)
@@ -169,6 +169,18 @@ class IMICRTS
@on_tick = block
end
def on_order(&block)
@on_order = block
end
def handle_order(type, order)
@components.each do |key, comp|
comp.on_order(type, order)
end
@on_order&.call(type, order)
end
def selected_draw
draw_radius
draw_gizmos

View File

@@ -1,6 +1,7 @@
class IMICRTS
class Map
attr_reader :tile_size, :tiles, :ores, :spawnpoints, :width, :height
def initialize(map_file:)
@tiled_map = TiledMap.new(map_file)
@@ -26,11 +27,10 @@ class IMICRTS
end
def add_terrain(x, y, tile_id)
if tile = @tiled_map.get_tile(tile_id - 1)
if (tile = @tiled_map.get_tile(tile_id - 1))
_tile = Tile.new(
position: CyberarmEngine::Vector.new(x * @tile_size, y * @tile_size, ZOrder::TILE),
image: tile.image,
visible: true,
type: tile.data.type.to_sym,
tile_size: @tile_size
)
@@ -43,11 +43,10 @@ class IMICRTS
end
def add_ore(x, y, tile_id)
if tile = @tiled_map.get_tile(tile_id - 1)
if (tile = @tiled_map.get_tile(tile_id - 1))
_ore = Tile.new(
position: CyberarmEngine::Vector.new(x * @tile_size, y * @tile_size, ZOrder::ORE),
image: tile.image,
visible: true,
type: nil,
tile_size: @tile_size
)
@@ -57,8 +56,8 @@ class IMICRTS
end
end
def draw(camera)
visible_tiles(camera).each do |tile|
def draw(observer)
visible_tiles(observer).each do |tile|
tile.image.draw(tile.position.x, tile.position.y, tile.position.z)
end
end
@@ -84,10 +83,11 @@ class IMICRTS
end
end
def visible_tiles(camera)
def visible_tiles(observer)
_tiles = []
visiblity_map = observer.visiblity_map
top_left = (camera.center - camera.position) - CyberarmEngine::Vector.new($window.width / 2, $window.height / 2) / camera.zoom
top_left = (observer.camera.center - observer.camera.position) - CyberarmEngine::Vector.new($window.width / 2, $window.height / 2) / observer.camera.zoom
top_left /= @tile_size
top_left.x = top_left.x.floor
@@ -95,8 +95,8 @@ class IMICRTS
# +1 to overdraw a bit to hide pop-in
_width = ((($window.width / @tile_size) + 2) / camera.zoom).ceil
_height = ((($window.height / @tile_size) + 2) / camera.zoom).ceil
_width = ((($window.width / @tile_size) + 2) / observer.camera.zoom).ceil
_height = ((($window.height / @tile_size) + 2) / observer.camera.zoom).ceil
_height.times do |y|
_width.times do |x|
@@ -104,12 +104,14 @@ class IMICRTS
next if _x < 0 || _x > @width
next if _y < 0 || _y > @height
if tile = tile_at(_x, _y)
_tiles.push(tile) if tile.visible
visible = visiblity_map.visible?(_x, _y)
if (tile = tile_at(_x, _y))
_tiles.push(tile) if visible
end
if ore = ore_at(_x, _y)
_tiles.push(ore) if ore.visible
if (ore = ore_at(_x, _y))
_tiles.push(ore) if visible
end
end
end
@@ -130,15 +132,15 @@ class IMICRTS
end
class Tile
attr_accessor :position, :grid_position, :image, :visible, :entity, :reserved, :type
def initialize(position:, image:, visible:, type:, tile_size:)
attr_accessor :position, :grid_position, :image, :entity, :reserved, :type
def initialize(position:, image:, type:, tile_size:)
@position = position
@grid_position = position.clone
@grid_position /= tile_size
@grid_position.x, @grid_position.y = @grid_position.x.floor, @grid_position.y.floor
@image = image
@visible = visible
@entity = nil
@reserved = nil
@type = type

View File

@@ -1,12 +1,7 @@
IMICRTS::Order.define_handler(IMICRTS::Order::BUILD_UNIT_COMPLETE, arguments: [:player_id, :entity_id]) do |order, director|
entity = director.player(order.player_id).entity(order.entity_id)
item = entity.component(:build_queue).queue.shift
spawn_point = entity.position.clone
spawn_point.y += 96 # TODO: Use entity defined spawnpoint
ent = entity.director.spawn_entity(player_id: entity.player.id, name: item.entity.name, position: spawn_point)
ent.target = entity.component(:waypoint).waypoint if entity.component(:waypoint)
entity.handle_order(IMICRTS::Order::BUILD_UNIT_COMPLETE, order)
end
IMICRTS::Order.define_serializer(IMICRTS::Order::BUILD_UNIT_COMPLETE) do |order, director|

View File

@@ -1,7 +1,7 @@
IMICRTS::Order.define_handler(IMICRTS::Order::CONSTRUCTION_COMPLETE, arguments: [:player_id, :entity_id]) do |order, director|
entity = director.player(order.player_id).entity(order.entity_id)
entity.component(:structure).data.construction_complete = true
entity.handle_order(IMICRTS::Order::CONSTRUCTION_COMPLETE, order)
end
IMICRTS::Order.define_serializer(IMICRTS::Order::CONSTRUCTION_COMPLETE) do |order, director|

View File

@@ -1,6 +1,6 @@
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
entity.handle_order(IMICRTS::Order::MOVE, order)
end
end

View File

@@ -1,6 +1,6 @@
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
entity.handle_order(IMICRTS::Order::STOP, order)
end
end

View File

@@ -1,13 +1,16 @@
class IMICRTS
class Player
attr_reader :id, :name, :color, :team, :entities, :orders, :camera, :spawnpoint
attr_reader :id, :name, :color, :team, :bot, :visiblity_map, :entities, :orders, :camera, :spawnpoint
attr_reader :selected_entities
def initialize(id:, spawnpoint:, name: nil, color: IMICRTS::TeamColors.values.sample, team: nil)
def initialize(id:, spawnpoint:, name: nil, color: IMICRTS::TeamColors.values.sample, team: nil, bot: false, visiblity_map:)
@id = id
@spawnpoint = spawnpoint
@name = name ? name : "Novice-#{id}"
@color = color
@team = team
@bot = bot
@visiblity_map = visiblity_map
@entities = []
@orders = []
@@ -24,7 +27,7 @@ class IMICRTS
@camera_moved = (@last_camera_position - @camera.position.clone).sum > @camera_move_threshold
@last_camera_position = @camera.position.clone
@entities.each { |ent| ent.tick(tick_id) }
@entities.each { |ent| ent.tick(tick_id); @visiblity_map.update(ent) }
end
def update
@@ -45,6 +48,7 @@ class IMICRTS
class ScheduledOrder
attr_reader :order_id, :tick_id, :serialized_order
def initialize(order_id, tick_id, serialized_order)
@order_id = order_id
@tick_id, @serialized_order = tick_id, serialized_order

View File

@@ -13,12 +13,18 @@ class IMICRTS
@director = Director.new(game: self, map: @options[:map], networking_mode: @options[:networking_mode])
@options[:players] ||= [
{ id: 0, team: 1, spawnpoint: @director.map.spawnpoints.last, color: :orange },
{ id: 1, team: 2, spawnpoint: @director.map.spawnpoints.first, color: :lightblue }
{ id: 0, team: 1, spawnpoint: @director.map.spawnpoints.last, color: :orange, bot: false },
{ id: 1, team: 2, spawnpoint: @director.map.spawnpoints.first, color: :lightblue, bot: :brutal }
]
@options[:players].each do |pl|
player = Player.new(id: pl[:id], spawnpoint: pl[:spawnpoint], team: pl[:team], color: TeamColors[pl[:color]])
player = nil
visiblity_map = VisibilityMap.new(width: @director.map.width, height: @director.map.height, tile_size: @director.map.tile_size)
unless pl[:bot]
player = Player.new(id: pl[:id], spawnpoint: pl[:spawnpoint], team: pl[:team], color: TeamColors[pl[:color]], visiblity_map: visiblity_map)
else
player = AIPlayer.new(id: pl[:id], spawnpoint: pl[:spawnpoint], team: pl[:team], color: TeamColors[pl[:color]], bot: pl[:bot], visiblity_map: visiblity_map)
end
@player = player if player.id == @options[:local_player_id]
@director.add_player(player)
end
@@ -82,7 +88,7 @@ class IMICRTS
super
@player.camera.draw do
@director.map.draw(@player.camera)
@director.map.draw(@player)
@director.entities.each(&:draw)
@selected_entities.each(&:selected_draw)

View File

@@ -48,25 +48,12 @@ class IMICRTS
return if @game.sidebar.hit?(@game.window.mouse_x, @game.window.mouse_y)
tile = @director.map.tile_at(vector.x, vector.y)
pp vector
return unless tile
position = tile.position + @director.map.tile_size / 2
# ent = @director.spawn_entity(
# player_id: @player.id, name: @entity,
# position: CyberarmEngine::Vector.new(position.x, position.y, ZOrder::BUILDING)
# )
@director.schedule_order(Order::CONSTRUCT, @player.id, vector, @entity)
# each_tile(vector) do |tile, space_required|
# if space_required == true
# tile.entity = ent
# else
# tile.reserved = ent
# end
# end
cancel_tool
end

36
lib/visiblity_map.rb Normal file
View File

@@ -0,0 +1,36 @@
class IMICRTS
class VisibilityMap
attr_reader :width, :height, :tile_size
def initialize(width:, height:, tile_size:)
@width = width
@height = height
@tile_size = tile_size
@map = Array.new(width * height, false)
end
def visible?(x, y)
@map.dig(index_at(x, y))
end
def index_at(x, y)
((y.clamp(0, @height - 1) * @width) + x.clamp(0, @width - 1))
end
def update(entity)
range = entity.sight_radius
pos = entity.position.clone / @tile_size
pos.x = pos.x.ceil - range
pos.y = pos.y.ceil - range
(range * 2).times do |y|
(range * 2).times do |x|
if not visible?(pos.x + x, pos.y + y).nil?
@map[index_at(pos.x + x, pos.y + y)] = true
end
end
end
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB