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.

This commit is contained in:
2021-01-01 10:18:16 -06:00
parent 0b3897451e
commit 74458dbfd0
22 changed files with 200 additions and 36 deletions

25
Gemfile.lock Normal file
View File

@@ -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

View File

@@ -1,5 +1,7 @@
class IMICRTS class IMICRTS
class BuildQueue < Component class BuildQueue < Component
attr_reader :queue
Item = Struct.new(:entity, :progress) Item = Struct.new(:entity, :progress)
def setup def setup
@queue = [] @queue = []

View File

@@ -3,11 +3,16 @@ class IMICRTS
attr_accessor :pathfinder attr_accessor :pathfinder
def update def update
if pathfinder && pathfinder.path_current_node if @parent.movement == :ground
rotate_towards(pathfinder.path_current_node.tile.position + @parent.director.map.tile_size / 2) if pathfinder && pathfinder.path_current_node
end 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 end
def rotate_towards(vector) def rotate_towards(vector)

23
lib/components/spawner.rb Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,7 @@
class IMICRTS class IMICRTS
class Director 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) def initialize(game:, map:, players: [], networking_mode:, tick_rate: 10)
@game = game @game = game
@map = map @map = map

View File

@@ -8,6 +8,8 @@ tiles = [
IMICRTS::Entity.define_entity(:barracks, :building, 400, "Builds and soldiers", tiles) do |entity| IMICRTS::Entity.define_entity(:barracks, :building, 400, "Builds and soldiers", tiles) do |entity|
entity.has(:building) entity.has(:building)
entity.has(:waypoint)
entity.has(:spawner)
entity.has(:build_queue) entity.has(:build_queue)
entity.radius = 44 entity.radius = 44

View File

@@ -8,6 +8,8 @@ tiles = [
IMICRTS::Entity.define_entity(:construction_yard, :building, 2_000, "Provides radar and builds construction workers", tiles) do |entity| IMICRTS::Entity.define_entity(:construction_yard, :building, 2_000, "Provides radar and builds construction workers", tiles) do |entity|
entity.has(:building) entity.has(:building)
entity.has(:waypoint)
entity.has(:spawner)
entity.has(:build_queue) entity.has(:build_queue)
entity.has(:sidebar_actions) entity.has(:sidebar_actions)
entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :construction_worker}) 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| emitters.each do |pos|
entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: pos) entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: pos, emitting: false)
end end
entity.on_tick do 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
end end

View File

@@ -8,6 +8,8 @@ tiles = [
IMICRTS::Entity.define_entity(:helipad, :building, 1_000, "Builds and rearms helicopters", tiles) do |entity| IMICRTS::Entity.define_entity(:helipad, :building, 1_000, "Builds and rearms helicopters", tiles) do |entity|
entity.has(:building) entity.has(:building)
entity.has(:waypoint)
entity.has(:spawner)
entity.has(:build_queue) entity.has(:build_queue)
entity.has(:sidebar_actions) entity.has(:sidebar_actions)
entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :helicopter}) entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :helicopter})

View File

@@ -34,6 +34,13 @@ IMICRTS::Entity.define_entity(:power_plant, :building, 800, "Generates power", t
entity.on_tick do entity.on_tick do
# entity.produce_power # entity.produce_power
if entity.component(:building).data.state == :idle
entity.particle_emitters.each do |emitter|
emitter.emitting = true
end
end
end end
# define_singleton_method(:produce_power) do # define_singleton_method(:produce_power) do

View File

@@ -23,8 +23,14 @@ IMICRTS::Entity.define_entity(:refinery, :building, 1_400, "Generates credits",
p1.x += 2 p1.x += 2
p1.y += 12 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 entity.on_tick do
if entity.component(:building).data.state == :idle
entity.particle_emitters.each do |emitter|
emitter.emitting = true
end
end
end end
end end

View File

@@ -8,6 +8,8 @@ tiles = [
IMICRTS::Entity.define_entity(:war_factory, :building, 2_000, "Builds units", tiles) do |entity| IMICRTS::Entity.define_entity(:war_factory, :building, 2_000, "Builds units", tiles) do |entity|
entity.has(:building) entity.has(:building)
entity.has(:waypoint)
entity.has(:spawner)
entity.has(:build_queue) entity.has(:build_queue)
entity.has(:sidebar_actions) entity.has(:sidebar_actions)
entity.component(:sidebar_actions).add(:add_to_build_queue, {entity: :jeep}) 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 = p1.clone
p2.y += 31 p2.y += 31
entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1) entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p1, emitting: false)
entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p2) entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: p2, emitting: false)
entity.on_tick do entity.on_tick do
item = entity.component(:build_queue).queue.first
entity.particle_emitters.each do |pe|
pe.emitting = !!item
end
end end
end end

View File

@@ -7,7 +7,7 @@ IMICRTS::Entity.define_entity(:jeep, :unit, 400, "Attacks ground targets") do |e
entity.max_health = 100.0 entity.max_health = 100.0
entity.body_image = "vehicles/jeep/jeep.png" 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).shell_image = "vehicles/jeep/jeep_turret_shell.png"
entity.component(:turret).center.y = 0.3125 entity.component(:turret).center.y = 0.3125

View File

@@ -102,7 +102,7 @@ class IMICRTS
def target=(entity) def target=(entity)
@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 end
def hit?(x_or_vector, y = nil) def hit?(x_or_vector, y = nil)
@@ -153,8 +153,12 @@ class IMICRTS
end end
def tick(tick_id) def tick(tick_id)
@components.values.each { |com| com.tick(tick_id) }
@on_tick.call if @on_tick @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 end
def on_tick(&block) def on_tick(&block)

View File

@@ -95,6 +95,10 @@ class IMICRTS
return _tiles return _tiles
end end
def world_to_grid(vector)
vector / @tile_size
end
def tile_at(x, y) def tile_at(x, y)
@tiles.dig(x, y) @tiles.dig(x, y)
end end

View File

@@ -80,6 +80,7 @@ class IMICRTS
:CANCEL_BUILD_ORDER, :CANCEL_BUILD_ORDER,
:BUILD_ORDER_COMPLETE, :BUILD_ORDER_COMPLETE,
:BUILDING_SET_WAYPOINT,
:BUILDING_POWER_STATE, :BUILDING_POWER_STATE,
:BUILDING_REPAIR, :BUILDING_REPAIR,
:BUILDING_SELL, :BUILDING_SELL,

View File

@@ -1,6 +1,5 @@
IMICRTS::Order.define_handler(IMICRTS::Order::BUILD_ORDER, arguments: [:player_id, :vector, :building]) do |order, director| 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) tile = director.map.tile_at(order.vector.x, order.vector.y)
p order.vector
position = tile.position + director.map.tile_size / 2 position = tile.position + director.map.tile_size / 2
ent = director.spawn_entity( ent = director.spawn_entity(

View File

@@ -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

View File

@@ -1,6 +1,6 @@
class IMICRTS class IMICRTS
class ParticleEmitter 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 @position = position
@direction = direction @direction = direction
@time_to_live = time_to_live @time_to_live = time_to_live
@@ -11,6 +11,7 @@ class IMICRTS
@images = images @images = images
@color = color @color = color
@jitter = jitter @jitter = jitter
@emitting = emitting
@born_at = Gosu.milliseconds @born_at = Gosu.milliseconds
@last_emitted_at = 0 @last_emitted_at = 0
@@ -44,7 +45,7 @@ class IMICRTS
def update def update
if @particles.count < @max_particles && Gosu.milliseconds >= @last_emitted_at + (1000.0 / @frequency) if @particles.count < @max_particles && Gosu.milliseconds >= @last_emitted_at + (1000.0 / @frequency)
emit emit if emit?
end end
@particles.each do |particle| @particles.each do |particle|
@@ -53,6 +54,14 @@ class IMICRTS
end end
end end
def emitting=(boolean)
@emitting= !!boolean
end
def emit?
@emitting
end
def die? def die?
Gosu.milliseconds >= @born_at + @time_to_live Gosu.milliseconds >= @born_at + @time_to_live
end end

View File

@@ -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]) @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) @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(@player)
@director.add_player(@player2)
@selected_entities = [] @selected_entities = []
@tool = set_tool(:entity_controller) @tool = set_tool(:entity_controller)
@@ -34,16 +36,25 @@ class IMICRTS
end end
end end
# TODO: implement tools @director.players.each do |player|
@director.spawn_entity( construction_yard = @director.spawn_entity(
player_id: @player.id, name: :construction_yard, player_id: player.id, name: :construction_yard,
position: CyberarmEngine::Vector.new(@player.spawnpoint.x, @player.spawnpoint.y, ZOrder::BUILDING) 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( @director.spawn_entity(
player_id: @player.id, name: :construction_worker, player_id: player.id, name: :construction_worker,
position: CyberarmEngine::Vector.new(@player.spawnpoint.x - 64, @player.spawnpoint.y + 64, ZOrder::GROUND_VEHICLE) position: CyberarmEngine::Vector.new(player.spawnpoint.x - 64, player.spawnpoint.y + 64, ZOrder::GROUND_VEHICLE)
) )
end
end end
def draw def draw

View File

@@ -40,11 +40,18 @@ class IMICRTS
@selection_start = @player.camera.transform(@game.window.mouse) @selection_start = @player.camera.transform(@game.window.mouse)
end end
when Gosu::MS_RIGHT when Gosu::MS_RIGHT
if @game.selected_entities.size > 0 if @player.selected_entities.size > 0
@director.schedule_order(Order::MOVE, @player.id, @player.camera.transform(@game.window.mouse)) 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 << 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.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 end
end end

View File

@@ -73,21 +73,14 @@ class IMICRTS
def can_use?(vector) def can_use?(vector)
return false if @game.sidebar.hit?(@game.window.mouse_x, @game.window.mouse_y) 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) if tile = @director.map.tile_at(vector.x, vector.y)
ent = Entity.get(@entity) ent = Entity.get(@entity)
origin = (tile.grid_position - 2) origin = (tile.grid_position - 2)
@director.each_tile(vector, @entity) do |tile, _data, x, y| @director.each_tile(vector, @entity) do |tile, _data, x, y|
if tile.entity || tile.reserved || tile.type != :ground || @director.map.ore_at(x, y) return false if tile.entity || tile.reserved || tile.type != :ground || @director.map.ore_at(x, y)
useable = false
break
end
end end
return useable
else else
return false return false
end end