diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..25d5c4e --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,25 @@ +GEM + remote: https://rubygems.org/ + specs: + clipboard (1.3.5) + cyberarm_engine (0.15.0) + clipboard (~> 1.3.5) + excon (~> 0.78.0) + gosu (~> 1.0.0) + gosu_more_drawables (~> 0.3) + excon (0.78.1) + gosu (1.0.0) + gosu_more_drawables (0.3.1) + mini_portile2 (2.4.0) + nokogiri (1.10.10) + mini_portile2 (~> 2.4.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + cyberarm_engine + nokogiri + +BUNDLED WITH + 2.2.3 diff --git a/lib/components/build_queue.rb b/lib/components/build_queue.rb index 25e26d4..71057c9 100644 --- a/lib/components/build_queue.rb +++ b/lib/components/build_queue.rb @@ -1,5 +1,7 @@ class IMICRTS class BuildQueue < Component + attr_reader :queue + Item = Struct.new(:entity, :progress) def setup @queue = [] diff --git a/lib/components/movement.rb b/lib/components/movement.rb index 314964d..b006d85 100644 --- a/lib/components/movement.rb +++ b/lib/components/movement.rb @@ -3,11 +3,16 @@ class IMICRTS attr_accessor :pathfinder def update - if pathfinder && pathfinder.path_current_node - rotate_towards(pathfinder.path_current_node.tile.position + @parent.director.map.tile_size / 2) - end + if @parent.movement == :ground + if pathfinder && pathfinder.path_current_node + rotate_towards(pathfinder.path_current_node.tile.position + @parent.director.map.tile_size / 2) + end - follow_path + follow_path + else + rotate_towards(@parent.target) + @parent.position -= (@parent.position.xy - @parent.target.xy).normalized * @parent.speed + end end def rotate_towards(vector) diff --git a/lib/components/spawner.rb b/lib/components/spawner.rb new file mode 100644 index 0000000..9ff48d2 --- /dev/null +++ b/lib/components/spawner.rb @@ -0,0 +1,23 @@ +class IMICRTS + class Spawner < Component + def tick(tick_id) + # TODO: Ensure that a build order is created before working on entity + + item = @parent.component(:build_queue).queue.first + + if item + item.progress += 1 + + if item.progress >= 100 # TODO: Define work units required for construction + @parent.component(:build_queue).queue.shift + + spawn_point = @parent.position.clone + spawn_point.y += 96 # TODO: Use one of entity's reserved tiles for spawning + + ent = @parent.director.spawn_entity(player_id: @parent.player.id, name: item.entity.name, position: spawn_point) + ent.target = @parent.component(:waypoint).waypoint if @parent.component(:waypoint) + end + end + end + end +end \ No newline at end of file diff --git a/lib/components/waypoint.rb b/lib/components/waypoint.rb new file mode 100644 index 0000000..31e71f3 --- /dev/null +++ b/lib/components/waypoint.rb @@ -0,0 +1,27 @@ +class IMICRTS + class Waypoint < Component + def setup + @waypoint = @parent.position.clone + @waypoint.y += @parent.director.map.tile_size + end + + def set(vector) + @waypoint = vector + end + + def waypoint + @waypoint.clone + end + + def draw + return unless @parent.player.selected_entities.include?(@parent) + + Gosu.draw_line( + @parent.position.x, @parent.position.y, @parent.player.color, + @waypoint.x, @waypoint.y, @parent.player.color, ZOrder::ENTITY_GIZMOS + ) + + Gosu.draw_circle(@waypoint.x, @waypoint.y, 4, 9, @parent.player.color, ZOrder::ENTITY_GIZMOS) + end + end +end \ No newline at end of file diff --git a/lib/director.rb b/lib/director.rb index 1ceabcb..9de68c3 100644 --- a/lib/director.rb +++ b/lib/director.rb @@ -1,6 +1,7 @@ class IMICRTS class Director - attr_reader :current_tick, :map, :game + attr_reader :current_tick, :map, :game, :players + def initialize(game:, map:, players: [], networking_mode:, tick_rate: 10) @game = game @map = map diff --git a/lib/entities/buildings/barracks.rb b/lib/entities/buildings/barracks.rb index 9d717be..e2db606 100644 --- a/lib/entities/buildings/barracks.rb +++ b/lib/entities/buildings/barracks.rb @@ -8,6 +8,8 @@ tiles = [ IMICRTS::Entity.define_entity(:barracks, :building, 400, "Builds and soldiers", tiles) do |entity| entity.has(:building) + entity.has(:waypoint) + entity.has(:spawner) entity.has(:build_queue) entity.radius = 44 diff --git a/lib/entities/buildings/construction_yard.rb b/lib/entities/buildings/construction_yard.rb index dedb78c..969796c 100644 --- a/lib/entities/buildings/construction_yard.rb +++ b/lib/entities/buildings/construction_yard.rb @@ -8,6 +8,8 @@ tiles = [ IMICRTS::Entity.define_entity(:construction_yard, :building, 2_000, "Provides radar and builds construction workers", tiles) do |entity| entity.has(:building) + entity.has(:waypoint) + entity.has(:spawner) entity.has(:build_queue) entity.has(:sidebar_actions) entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :construction_worker}) @@ -39,9 +41,18 @@ IMICRTS::Entity.define_entity(:construction_yard, :building, 2_000, "Provides ra emitters.each do |pos| - entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: pos) + entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: pos, emitting: false) end entity.on_tick do + + if entity.component(:building).data.state == :idle + item = entity.component(:build_queue).queue.first + + entity.particle_emitters.each_with_index do |emitter, i| + emitter.emitting = true + emitter.emitting = !!item if i < 2 + end + end end end diff --git a/lib/entities/buildings/helipad.rb b/lib/entities/buildings/helipad.rb index b5a6a37..4bde29c 100644 --- a/lib/entities/buildings/helipad.rb +++ b/lib/entities/buildings/helipad.rb @@ -8,6 +8,8 @@ tiles = [ IMICRTS::Entity.define_entity(:helipad, :building, 1_000, "Builds and rearms helicopters", tiles) do |entity| entity.has(:building) + entity.has(:waypoint) + entity.has(:spawner) entity.has(:build_queue) entity.has(:sidebar_actions) entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :helicopter}) diff --git a/lib/entities/buildings/power_plant.rb b/lib/entities/buildings/power_plant.rb index 33abde8..053af19 100644 --- a/lib/entities/buildings/power_plant.rb +++ b/lib/entities/buildings/power_plant.rb @@ -34,6 +34,13 @@ IMICRTS::Entity.define_entity(:power_plant, :building, 800, "Generates power", t entity.on_tick do # entity.produce_power + + if entity.component(:building).data.state == :idle + + entity.particle_emitters.each do |emitter| + emitter.emitting = true + end + end end # define_singleton_method(:produce_power) do diff --git a/lib/entities/buildings/refinery.rb b/lib/entities/buildings/refinery.rb index a77259b..9168fb1 100644 --- a/lib/entities/buildings/refinery.rb +++ b/lib/entities/buildings/refinery.rb @@ -23,8 +23,14 @@ IMICRTS::Entity.define_entity(:refinery, :building, 1_400, "Generates credits", p1.x += 2 p1.y += 12 - entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1) + entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1, emitting: false) entity.on_tick do + if entity.component(:building).data.state == :idle + + entity.particle_emitters.each do |emitter| + emitter.emitting = true + end + end end end diff --git a/lib/entities/buildings/war_factory.rb b/lib/entities/buildings/war_factory.rb index 3acc8b2..cf45337 100644 --- a/lib/entities/buildings/war_factory.rb +++ b/lib/entities/buildings/war_factory.rb @@ -8,6 +8,8 @@ tiles = [ IMICRTS::Entity.define_entity(:war_factory, :building, 2_000, "Builds units", tiles) do |entity| entity.has(:building) + entity.has(:waypoint) + entity.has(:spawner) entity.has(:build_queue) entity.has(:sidebar_actions) entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :jeep}) @@ -29,9 +31,14 @@ IMICRTS::Entity.define_entity(:war_factory, :building, 2_000, "Builds units", ti p2 = p1.clone p2.y += 31 - entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1) - entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p2) + entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1, emitting: false) + entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p2, emitting: false) entity.on_tick do + item = entity.component(:build_queue).queue.first + + entity.particle_emitters.each do |pe| + pe.emitting = !!item + end end end diff --git a/lib/entities/units/jeep.rb b/lib/entities/units/jeep.rb index 393b8d0..8700b3a 100644 --- a/lib/entities/units/jeep.rb +++ b/lib/entities/units/jeep.rb @@ -7,7 +7,7 @@ IMICRTS::Entity.define_entity(:jeep, :unit, 400, "Attacks ground targets") do |e entity.max_health = 100.0 entity.body_image = "vehicles/jeep/jeep.png" - entity.shell_image = "vehicles/jeep/tank_shell.png" + entity.shell_image = "vehicles/jeep/jeep_shell.png" entity.component(:turret).shell_image = "vehicles/jeep/jeep_turret_shell.png" entity.component(:turret).center.y = 0.3125 diff --git a/lib/entity.rb b/lib/entity.rb index d416d44..5bd14b5 100644 --- a/lib/entity.rb +++ b/lib/entity.rb @@ -102,7 +102,7 @@ class IMICRTS def target=(entity) @target = entity - component(:movement).pathfinder = @director.find_path(player: @player, entity: self, goal: @target) if component(:movement) + component(:movement).pathfinder = @director.find_path(player: @player, entity: self, goal: @target) if component(:movement) && @movement == :ground end def hit?(x_or_vector, y = nil) @@ -153,8 +153,12 @@ class IMICRTS end def tick(tick_id) + @components.values.each { |com| com.tick(tick_id) } + @on_tick.call if @on_tick - component(:building).construction_work(1) if component(:building) + data.assigned_construction_workers ||= 4 + data.construction_speed ||= 1 + component(:building).construction_work(data.assigned_construction_workers * data.construction_speed) if component(:building) end def on_tick(&block) diff --git a/lib/map.rb b/lib/map.rb index 239673d..27b00e5 100644 --- a/lib/map.rb +++ b/lib/map.rb @@ -95,6 +95,10 @@ class IMICRTS return _tiles end + def world_to_grid(vector) + vector / @tile_size + end + def tile_at(x, y) @tiles.dig(x, y) end diff --git a/lib/order.rb b/lib/order.rb index 82e0036..dbb0832 100644 --- a/lib/order.rb +++ b/lib/order.rb @@ -80,6 +80,7 @@ class IMICRTS :CANCEL_BUILD_ORDER, :BUILD_ORDER_COMPLETE, + :BUILDING_SET_WAYPOINT, :BUILDING_POWER_STATE, :BUILDING_REPAIR, :BUILDING_SELL, diff --git a/lib/orders/build_order.rb b/lib/orders/build_order.rb index 6e14f0e..3d653eb 100644 --- a/lib/orders/build_order.rb +++ b/lib/orders/build_order.rb @@ -1,6 +1,5 @@ IMICRTS::Order.define_handler(IMICRTS::Order::BUILD_ORDER, arguments: [:player_id, :vector, :building]) do |order, director| tile = director.map.tile_at(order.vector.x, order.vector.y) - p order.vector position = tile.position + director.map.tile_size / 2 ent = director.spawn_entity( diff --git a/lib/orders/building_set_waypoint.rb b/lib/orders/building_set_waypoint.rb new file mode 100644 index 0000000..d4ff4eb --- /dev/null +++ b/lib/orders/building_set_waypoint.rb @@ -0,0 +1,18 @@ +IMICRTS::Order.define_handler(IMICRTS::Order::BUILDING_SET_WAYPOINT, arguments: [:player_id, :entity_id, :vector]) do |order, director| + director.player(order.player_id).entity(order.entity_id).component(:waypoint).set(order.vector) +end + +IMICRTS::Order.define_serializer(IMICRTS::Order::BUILDING_SET_WAYPOINT) do |order, director| + # Order ID | Player ID | Entity ID | Target X | Target Y + # char | char | integer | double | double + + [IMICRTS::Order::BUILDING_SET_WAYPOINT, order.player_id, order.entity_id, order.vector.x, order.vector.y].pack("CCNGG") +end + +IMICRTS::Order.define_deserializer(IMICRTS::Order::BUILDING_SET_WAYPOINT) do |string, director| + # String fed into deserializer has Order ID removed + # Player ID | Entity ID | Target X | Target Y + # char | integer | double | double + data = string.unpack("CNGG") + [data[0], data[1], CyberarmEngine::Vector.new(data[2], data[3])] +end diff --git a/lib/particle_emitter.rb b/lib/particle_emitter.rb index 284e2ed..4f57d2d 100644 --- a/lib/particle_emitter.rb +++ b/lib/particle_emitter.rb @@ -1,6 +1,6 @@ class IMICRTS class ParticleEmitter - def initialize(position:, direction: CyberarmEngine::Vector.up, time_to_live: 1000, particle_time_to_live: 500, speed: 10.0, max_particles: 128, frequency: 10.0, images: [], color: Gosu::Color::WHITE.dup, jitter: 10.0) + def initialize(position:, direction: CyberarmEngine::Vector.up, time_to_live: 1000, particle_time_to_live: 500, speed: 10.0, max_particles: 128, frequency: 10.0, images: [], color: Gosu::Color::WHITE.dup, jitter: 10.0, emitting: true) @position = position @direction = direction @time_to_live = time_to_live @@ -11,6 +11,7 @@ class IMICRTS @images = images @color = color @jitter = jitter + @emitting = emitting @born_at = Gosu.milliseconds @last_emitted_at = 0 @@ -44,7 +45,7 @@ class IMICRTS def update if @particles.count < @max_particles && Gosu.milliseconds >= @last_emitted_at + (1000.0 / @frequency) - emit + emit if emit? end @particles.each do |particle| @@ -53,6 +54,14 @@ class IMICRTS end end + def emitting=(boolean) + @emitting= !!boolean + end + + def emit? + @emitting + end + def die? Gosu.milliseconds >= @born_at + @time_to_live end diff --git a/lib/states/game.rb b/lib/states/game.rb index 1d3bacb..cba3563 100644 --- a/lib/states/game.rb +++ b/lib/states/game.rb @@ -10,7 +10,9 @@ class IMICRTS @director = Director.new(game: self, map: Map.new(map_file: "maps/test_map.tmx"), networking_mode: @options[:networking_mode]) @player = Player.new(id: 0, spawnpoint: @director.map.spawnpoints.last) + @player2 = Player.new(id: 1, spawnpoint: @director.map.spawnpoints.first) @director.add_player(@player) + @director.add_player(@player2) @selected_entities = [] @tool = set_tool(:entity_controller) @@ -34,16 +36,25 @@ class IMICRTS end end - # TODO: implement tools - @director.spawn_entity( - player_id: @player.id, name: :construction_yard, - position: CyberarmEngine::Vector.new(@player.spawnpoint.x, @player.spawnpoint.y, ZOrder::BUILDING) - ) + @director.players.each do |player| + construction_yard = @director.spawn_entity( + player_id: player.id, name: :construction_yard, + position: CyberarmEngine::Vector.new(player.spawnpoint.x, player.spawnpoint.y, ZOrder::BUILDING) + ) + construction_yard.component(:building).data.construction_progress = 100 + @director.each_tile(@director.map.world_to_grid(construction_yard.position), construction_yard.name) do |tile, space_required| + if space_required == true + tile.entity = construction_yard + else + tile.reserved = construction_yard + end + end - @director.spawn_entity( - player_id: @player.id, name: :construction_worker, - position: CyberarmEngine::Vector.new(@player.spawnpoint.x - 64, @player.spawnpoint.y + 64, ZOrder::GROUND_VEHICLE) - ) + @director.spawn_entity( + player_id: player.id, name: :construction_worker, + position: CyberarmEngine::Vector.new(player.spawnpoint.x - 64, player.spawnpoint.y + 64, ZOrder::GROUND_VEHICLE) + ) + end end def draw diff --git a/lib/tools/entity_controller.rb b/lib/tools/entity_controller.rb index be6250e..f8e0211 100644 --- a/lib/tools/entity_controller.rb +++ b/lib/tools/entity_controller.rb @@ -40,11 +40,18 @@ class IMICRTS @selection_start = @player.camera.transform(@game.window.mouse) end when Gosu::MS_RIGHT - if @game.selected_entities.size > 0 - @director.schedule_order(Order::MOVE, @player.id, @player.camera.transform(@game.window.mouse)) + if @player.selected_entities.size > 0 + if @player.selected_entities.any? { |ent| ent.component(:movement) } + @director.schedule_order(Order::MOVE, @player.id, @player.camera.transform(@game.window.mouse)) - @game.overlays << Game::Overlay.new(Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/cursors/move.png"), @player.camera.transform(@game.window.mouse), 0, 255) - @game.overlays.last.position.z = ZOrder::OVERLAY + @game.overlays << Game::Overlay.new(Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/cursors/move.png"), @player.camera.transform(@game.window.mouse), 0, 255) + @game.overlays.last.position.z = ZOrder::OVERLAY + elsif @player.selected_entities.size == 1 && @player.selected_entities.first.component(:waypoint) + @director.schedule_order(Order::BUILDING_SET_WAYPOINT, @player.id, @player.selected_entities.first.id, @player.camera.transform(@game.window.mouse)) + + @game.overlays << Game::Overlay.new(Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/cursors/move.png"), @player.camera.transform(@game.window.mouse), 0, 255) + @game.overlays.last.position.z = ZOrder::OVERLAY + end end end end diff --git a/lib/tools/place_entity.rb b/lib/tools/place_entity.rb index eb2799a..b177efa 100644 --- a/lib/tools/place_entity.rb +++ b/lib/tools/place_entity.rb @@ -73,21 +73,14 @@ class IMICRTS def can_use?(vector) return false if @game.sidebar.hit?(@game.window.mouse_x, @game.window.mouse_y) - useable = true - done = false if tile = @director.map.tile_at(vector.x, vector.y) ent = Entity.get(@entity) origin = (tile.grid_position - 2) @director.each_tile(vector, @entity) do |tile, _data, x, y| - if tile.entity || tile.reserved || tile.type != :ground || @director.map.ore_at(x, y) - useable = false - break - end + return false if tile.entity || tile.reserved || tile.type != :ground || @director.map.ore_at(x, y) end - - return useable else return false end