diff --git a/i-mic-rts.rb b/i-mic-rts.rb index 21bf384..ef038fa 100755 --- a/i-mic-rts.rb +++ b/i-mic-rts.rb @@ -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" diff --git a/lib/ai/ai_player.rb b/lib/ai/ai_player.rb new file mode 100644 index 0000000..b1536db --- /dev/null +++ b/lib/ai/ai_player.rb @@ -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 \ No newline at end of file diff --git a/lib/camera.rb b/lib/camera.rb index dd0c941..28d480c 100644 --- a/lib/camera.rb +++ b/lib/camera.rb @@ -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 diff --git a/lib/component.rb b/lib/component.rb index c258fd6..be3dba1 100644 --- a/lib/component.rb +++ b/lib/component.rb @@ -44,6 +44,9 @@ class IMICRTS def tick(tick_id) end + + def on_order(type, order) + end end end diff --git a/lib/components/movement.rb b/lib/components/movement.rb index 3bc206a..359c4ad 100644 --- a/lib/components/movement.rb +++ b/lib/components/movement.rb @@ -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 diff --git a/lib/components/spawner.rb b/lib/components/spawner.rb index 3af4ff5..15355e7 100644 --- a/lib/components/spawner.rb +++ b/lib/components/spawner.rb @@ -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 \ No newline at end of file diff --git a/lib/components/structure.rb b/lib/components/structure.rb index 8301830..c1defc1 100644 --- a/lib/components/structure.rb +++ b/lib/components/structure.rb @@ -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 diff --git a/lib/components/waypoint.rb b/lib/components/waypoint.rb index 6b3251f..e86188a 100644 --- a/lib/components/waypoint.rb +++ b/lib/components/waypoint.rb @@ -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 diff --git a/lib/entities/structures/helipad.rb b/lib/entities/structures/helipad.rb index 9f0b865..b6ed2d4 100644 --- a/lib/entities/structures/helipad.rb +++ b/lib/entities/structures/helipad.rb @@ -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 diff --git a/lib/entities/structures/refinery.rb b/lib/entities/structures/refinery.rb index efcabdf..f41bb47 100644 --- a/lib/entities/structures/refinery.rb +++ b/lib/entities/structures/refinery.rb @@ -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 diff --git a/lib/entities/units/helicopter.rb b/lib/entities/units/helicopter.rb index fe4f265..b4ae09b 100644 --- a/lib/entities/units/helicopter.rb +++ b/lib/entities/units/helicopter.rb @@ -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" diff --git a/lib/entity.rb b/lib/entity.rb index 5f67888..7660cf7 100644 --- a/lib/entity.rb +++ b/lib/entity.rb @@ -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 diff --git a/lib/map.rb b/lib/map.rb index 6c1a0e4..1945a25 100644 --- a/lib/map.rb +++ b/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 diff --git a/lib/orders/build_unit_complete.rb b/lib/orders/build_unit_complete.rb index 82373af..9f71d3a 100644 --- a/lib/orders/build_unit_complete.rb +++ b/lib/orders/build_unit_complete.rb @@ -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| diff --git a/lib/orders/construction_complete.rb b/lib/orders/construction_complete.rb index da56e0b..f24b241 100644 --- a/lib/orders/construction_complete.rb +++ b/lib/orders/construction_complete.rb @@ -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| diff --git a/lib/orders/move.rb b/lib/orders/move.rb index 61570df..eda2ed3 100644 --- a/lib/orders/move.rb +++ b/lib/orders/move.rb @@ -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 diff --git a/lib/orders/stop.rb b/lib/orders/stop.rb index 656beed..3511cdc 100644 --- a/lib/orders/stop.rb +++ b/lib/orders/stop.rb @@ -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 diff --git a/lib/player.rb b/lib/player.rb index a9aac2b..e8be887 100644 --- a/lib/player.rb +++ b/lib/player.rb @@ -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 diff --git a/lib/states/game.rb b/lib/states/game.rb index c0c0e2e..6a5a63d 100644 --- a/lib/states/game.rb +++ b/lib/states/game.rb @@ -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) diff --git a/lib/tools/place_entity.rb b/lib/tools/place_entity.rb index 587cb26..6c5536e 100644 --- a/lib/tools/place_entity.rb +++ b/lib/tools/place_entity.rb @@ -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 diff --git a/lib/visiblity_map.rb b/lib/visiblity_map.rb new file mode 100644 index 0000000..54c3241 --- /dev/null +++ b/lib/visiblity_map.rb @@ -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 \ No newline at end of file diff --git a/screenshots/screenshot-game.png b/screenshots/screenshot-game.png new file mode 100644 index 0000000..5894cb2 Binary files /dev/null and b/screenshots/screenshot-game.png differ