diff --git a/.gitignore b/.gitignore index 3e71846..fa6645c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ data/settings.json -data/replays/*.replay -data/saves/*.save +data/replays/*.dat +data/saves/*.dat doc/ .yardoc/ \ No newline at end of file diff --git a/i-mic-rts.rb b/i-mic-rts.rb index ef038fa..46ab689 100755 --- a/i-mic-rts.rb +++ b/i-mic-rts.rb @@ -8,6 +8,7 @@ end require "json" require "socket" +require "digest/sha2" require "nokogiri" @@ -20,6 +21,7 @@ require_relative "lib/camera" require_relative "lib/setting" require_relative "lib/team_colors" require_relative "lib/constants" +require_relative "lib/game_save" require_relative "lib/states/boot" require_relative "lib/states/game" diff --git a/lib/director.rb b/lib/director.rb index 018f1c8..8db10cb 100644 --- a/lib/director.rb +++ b/lib/director.rb @@ -2,7 +2,7 @@ class IMICRTS class Director attr_reader :current_tick, :map, :game, :players - def initialize(game:, map:, players: [], networking_mode:, tick_rate: 10, local_game: true, replay: false) + def initialize(game:, map:, players:, networking_mode:, tick_rate: 15, local_game: true, replay: false, game_save: false, gamesave_file: nil) @game = game @map = map @players = players @@ -11,6 +11,10 @@ class IMICRTS @tick_rate = tick_rate @local_game = local_game @replay = replay + @game_save = game_save + @gamesave_file = gamesave_file ? gamesave_file : "#{IMICRTS::GAME_ROOT_PATH}/data/saves/savegame_#{Time.now.to_i}.dat" + @game_save_host = GameSave.new(mode: :write, gamesave_file: @gamesave_file, players: players, map_file: @map.map_file, gamesave: true) + @game_save_host.write_header @last_tick_at = Gosu.milliseconds @tick_time = 1000.0 / @tick_rate @@ -25,8 +29,8 @@ class IMICRTS @replay end - def add_player(player) - @players << player + def game_save? + @game_save end def update @@ -35,6 +39,7 @@ class IMICRTS tick @connection.update + @game_save_host&.feed_tick(@current_tick) if @replay end @players.each { |player| player.update } @@ -58,6 +63,7 @@ class IMICRTS order_args = o.deserialize(order_data[1..order_data.length - 1], self) execute_order(o.id, *order_args) + @game_save_host&.write_order(order_data) unless @replay player.orders.delete(order) @@ -140,6 +146,7 @@ class IMICRTS def finalize @server&.stop @connection&.finalize + @game_save_host&.finalize end end end diff --git a/lib/game_save.rb b/lib/game_save.rb new file mode 100644 index 0000000..7b9561f --- /dev/null +++ b/lib/game_save.rb @@ -0,0 +1,50 @@ +class IMICRTS + class GameSave + def initialize(mode:, gamesave_file:, map_file: nil, players: nil, gamesave: false, player_id: nil) + @mode = mode + @gamesave_file = File.open(gamesave_file, "#{@mode == :write ? 'w' : 'r'}") + @map_file = map_file + @players = players + @gamesave = gamesave + @player_id = player_id + + @version = IMICRTS::VERSION + end + + def parse + # + end + + def feed_tick(tick_id) + + end + + def write_header + player_data = @players.map do |player| + { + id: player.id, + name: player.name, + team: player.team, + spawnpoint: player.spawnpoint, + color: player.color&.gl, + bot: player.bot + } + end + +s = %{#{@version} +#{@map_file}?#{Digest::SHA256.digest(File.read("#{IMICRTS::GAME_ROOT_PATH}/assets/#{@map_file}"))} +#{@gamesave ? "GAMESAVE?#{@player_id}" : "REPLAY"} +#{JSON.dump(player_data)}} + + @gamesave_file.puts(s) + end + + def write_order(raw_order) + @gamesave_file.puts(raw_order) + end + + def finalize + @gamesave_file&.close + end + end +end \ No newline at end of file diff --git a/lib/map.rb b/lib/map.rb index 1945a25..073b4a5 100644 --- a/lib/map.rb +++ b/lib/map.rb @@ -1,8 +1,9 @@ class IMICRTS class Map - attr_reader :tile_size, :tiles, :ores, :spawnpoints, :width, :height + attr_reader :tile_size, :tiles, :ores, :spawnpoints, :width, :height, :map_file def initialize(map_file:) + @map_file = map_file @tiled_map = TiledMap.new(map_file) @width = @tiled_map.width diff --git a/lib/states/game.rb b/lib/states/game.rb index 6a5a63d..2b714de 100644 --- a/lib/states/game.rb +++ b/lib/states/game.rb @@ -11,24 +11,26 @@ class IMICRTS @options[:map] ||= Map.new(map_file: "maps/test_map.tmx") @options[:local_player_id] ||= 0 - @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, bot: false }, - { id: 1, team: 2, spawnpoint: @director.map.spawnpoints.first, color: :lightblue, bot: :brutal } + { id: 0, name: "0xdeadbeef", team: 1, spawnpoint: "B", color: :orange, bot: false }, + { id: 1, name: "BrutalAI", team: 2, spawnpoint: "A", color: :lightblue, bot: :brutal } ] - @options[:players].each do |pl| + players = @options[:players].map do |pl| player = nil - visiblity_map = VisibilityMap.new(width: @director.map.width, height: @director.map.height, tile_size: @director.map.tile_size) + visiblity_map = VisibilityMap.new(width: @options[:map].width, height: @options[:map].height, tile_size: @options[: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) + player = Player.new(id: pl[:id], name: pl[:name], 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) + player = AIPlayer.new(id: pl[:id], name: pl[:name], 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) + + player end + @director = Director.new(game: self, players: players, map: @options[:map], networking_mode: @options[:networking_mode]) + @selected_entities = [] @tool = set_tool(:entity_controller) @overlays = [] @@ -61,9 +63,11 @@ class IMICRTS end @director.players.each do |player| + spawnpoint = @director.map.spawnpoints[player.spawnpoint.bytes.first - 65] + construction_yard = @director.spawn_entity( player_id: player.id, name: :construction_yard, - position: CyberarmEngine::Vector.new(player.spawnpoint.x, player.spawnpoint.y, ZOrder::BUILDING) + position: CyberarmEngine::Vector.new(spawnpoint.x, spawnpoint.y, ZOrder::BUILDING) ) construction_yard.component(:structure).data.construction_progress = Entity.get(construction_yard.name).build_steps construction_yard.component(:structure).data.construction_complete = true @@ -77,7 +81,7 @@ class IMICRTS @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) + position: CyberarmEngine::Vector.new(construction_yard.position.x - 64, construction_yard.position.y + 64, ZOrder::GROUND_VEHICLE) ) end