From 74458dbfd02c718c32907ba3e0497296ef6a8c0e Mon Sep 17 00:00:00 2001 From: cyberarm Date: Fri, 1 Jan 2021 10:18:16 -0600 Subject: [PATCH] Added spawner and waypoint components, added building_set_waypoint order, particle emitters can now toggle their emission, buildings can now build and spawn units, buildings now dynamically emit smoke when working and no longer when building, added player2 to default game for testing. --- Gemfile.lock | 25 ++++++++++++++++++ lib/components/build_queue.rb | 2 ++ lib/components/movement.rb | 13 ++++++--- lib/components/spawner.rb | 23 ++++++++++++++++ lib/components/waypoint.rb | 27 +++++++++++++++++++ lib/director.rb | 3 ++- lib/entities/buildings/barracks.rb | 2 ++ lib/entities/buildings/construction_yard.rb | 13 ++++++++- lib/entities/buildings/helipad.rb | 2 ++ lib/entities/buildings/power_plant.rb | 7 +++++ lib/entities/buildings/refinery.rb | 8 +++++- lib/entities/buildings/war_factory.rb | 11 ++++++-- lib/entities/units/jeep.rb | 2 +- lib/entity.rb | 8 ++++-- lib/map.rb | 4 +++ lib/order.rb | 1 + lib/orders/build_order.rb | 1 - lib/orders/building_set_waypoint.rb | 18 +++++++++++++ lib/particle_emitter.rb | 13 +++++++-- lib/states/game.rb | 29 ++++++++++++++------- lib/tools/entity_controller.rb | 15 ++++++++--- lib/tools/place_entity.rb | 9 +------ 22 files changed, 200 insertions(+), 36 deletions(-) create mode 100644 Gemfile.lock create mode 100644 lib/components/spawner.rb create mode 100644 lib/components/waypoint.rb create mode 100644 lib/orders/building_set_waypoint.rb 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