mirror of
https://github.com/cyberarm/i-mic-rts.git
synced 2025-12-12 22:52:33 +00:00
added on_order handler to entities and components, added visibility map
This commit is contained in:
@@ -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
15
lib/ai/ai_player.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,9 @@ class IMICRTS
|
||||
|
||||
def tick(tick_id)
|
||||
end
|
||||
|
||||
def on_order(type, order)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
36
lib/map.rb
36
lib/map.rb
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
36
lib/visiblity_map.rb
Normal 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
|
||||
BIN
screenshots/screenshot-game.png
Normal file
BIN
screenshots/screenshot-game.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
Reference in New Issue
Block a user