오픈 소스 모험: 에피소드 52: BATTLETECH 무기 데이터 내보내기

26194 단어 rubygamedev
이제 BATTLETECH 무기 데이터의 대화형 시각화를 만들어 보겠습니다. 내가 보통하는 방식은 다음과 같습니다.
  • 게임 데이터를 살펴보고 사전 처리한 다음 모두 JSON으로 내보내는 Ruby 프로그램
  • 이 사전 처리된 단일 JSON으로만 작동하고 복잡성을 처리할 필요가 없는 일부 JavaScript 프런트엔드 프로그램

  • Hearts of Iron IV - Division Designer과 비슷한 작업을 했습니다. 불행하게도 전체 재작성 없이는 최신 HoI4 버전으로 업데이트할 수 없습니다.

    수출업자




    #!/usr/bin/env ruby
    
    require "json"
    require "memoist"
    require "pathname"
    require "pry"
    
    class String
      def camelize
        gsub(/_([a-z])/) { $1.upcase }
      end
    end
    
    class AmmoBox
      extend Memoist
      attr_reader :data, :path
    
      def initialize(path)
        @path = path
        @data = JSON.parse(path.read)
      end
    
      memoize def id
        @data["AmmoID"].sub(/\AAmmunition_/, "")
      end
    
      def tonnage
        @data["Tonnage"]
      end
    
      def capacity
        @data["Capacity"]
      end
    end
    
    class Weapon
      extend Memoist
      attr_reader :game, :data, :path
    
      def initialize(game, path)
        @game = game
        @path = path
        @data = JSON.parse(path.read)
      end
    
      memoize def name
        bonuses = [bonus_a, bonus_b].compact
        if bonuses.empty?
          base_name
        else
          "#{base_name} (#{bonuses.join(", ")})"
        end
      end
    
      memoize def base_name
        [
          data["Description"]["Name"],
          data["Description"]["UIName"],
        ].compact.last.gsub(" +", "+")
      end
    
      memoize def bonus_a
        data["BonusValueA"] == "" ? nil : data["BonusValueA"].gsub(/[a-z]\K\./, "").gsub(/[\+\-]\K /, "")
      end
    
      memoize def bonus_b
        data["BonusValueB"] == "" ? nil : data["BonusValueB"].gsub(/[a-z]\K\./, "").gsub(/[\+\-]\K /, "")
      end
    
      def category
        @data["Category"]
      end
    
      def subtype
        @data["WeaponSubType"]
      end
    
      def tonnage
        @data["Tonnage"]
      end
    
      def damage
        shots * base_damage
      end
    
      def base_damage
        @data["Damage"]
      end
    
      def shots
        @data["ShotsWhenFired"]
      end
    
      def heat
        @data["HeatGenerated"]
      end
    
      def ammo_per_shot
        @data["ShotsWhenFired"] * data["ProjectilesPerShot"]
      end
    
      def heat_tonnage
        heat / 3.0
      end
    
      # 10 rounds of shootnig at target
      def ammo_tonnage_per_shot
        @game.ammo_weights.fetch(ammo_category) * ammo_per_shot
      end
    
      def total_tonnage
        tonnage + heat_tonnage + ammo_tonnage
      end
    
      def ammo_category
        @data["ammoCategoryID"] || @data["AmmoCategory"]
      end
    
      def purchasable?
        @data["Description"]["Purchasable"]
      end
    
      def weapon_effect
        @data["WeaponEffectID"]
      end
    
      def ignore?
        [
          category == "Melee",
          name == "AI Laser",
          subtype == "TAG",
          subtype == "Narc",
          subtype =~ /\ACOIL/,
          weapon_effect == "WeaponEffect-Artillery_MechMortar",
          weapon_effect == "WeaponEffect-Artillery_Thumper",
        ].any?
      end
    
      def min_range
        @data["MinRange"]
      end
    
      def max_range
        @data["MaxRange"]
      end
    
      def indirect_fire
        @data["IndirectFireCapable"]
      end
    
      def as_json
        {
          name:,
          tonnage:,
          heat:,
          shots:,
          base_damage:,
          ammo_tonnage_per_shot:,
          min_range:,
          max_range:,
          indirect_fire:,
        }.transform_keys(&:to_s).transform_keys(&:camelize)
      end
    end
    
    class BattleTechGame
      extend Memoist
    
      def initialize(game_root, *dlc_roots)
        @game_root = Pathname(game_root)
        @dlc_roots = dlc_roots.map{|path| Pathname(path)}
      end
    
      memoize def data_root
        @game_root + "BattleTech_Data/StreamingAssets/data"
      end
    
      def roots
        [data_root, *@dlc_roots]
      end
    
      memoize def weapon_files
        roots
          .flat_map{|root| root.glob("weapon/*.json")}
          .select{|n| n.basename.to_s != "WeaponTemplate.json"}
      end
    
      memoize def weapons
        weapon_files.map{|path| Weapon.new(self, path)}
      end
    
      memoize def ammobox_files
        roots
          .flat_map{|root| root.glob("ammunitionBox/*.json")}
          .select{|n| n.basename.to_s != "AmmoBoxTemplate.json"}
      end
    
      memoize def ammoboxes
        ammobox_files.map{|path| AmmoBox.new(path)}
      end
    
      memoize def ammo_weights
        # MG box occurs twice, but with same ratio
        ammoboxes.to_h{|a| [a.id, a.tonnage.to_f/a.capacity]}.merge("NotSet" => 0.0)
      end
    
      def inspect
        "BattechGame"
      end
    
      def as_json
        weapons
          .reject(&:ignore?)
          .map(&:as_json)
      end
    end
    
    game = BattleTechGame.new(*ARGV)
    puts JSON.pretty_generate(game.as_json)
    


    코드에는 주로 다음과 같은 몇 가지 흥미로운 사항이 있습니다.
  • as_json 객체를 JSON 가능 데이터로 변환하지만 최종 데이터String(to_json가 하듯이)를 생성하지 않으므로 구성 가능합니다
  • .
  • Ruby 3 스타일 해시{name:}는 Ruby 2.x에서 {name: name} 또는 Ruby 1.x에서 {:name => name}를 의미합니다.
  • .transform_keys(&:camelize) Hash를 불쾌한 카멜 케이싱
  • 을 사용하여 JavaScript와 유사한 것으로 변환합니다.

    생성된 JSON



    다음은 단편입니다.

    [
      {
        "name": "AC/10",
        "tonnage": 12,
        "heat": 12,
        "shots": 1,
        "baseDamage": 60,
        "ammoTonnagePerShot": 0.125,
        "minRange": 0,
        "maxRange": 450,
        "indirectFire": false
      },
      ...
      {
        "name": "LRM5++ (+2 Dmg)",
        "tonnage": 2,
        "heat": 6,
        "shots": 5,
        "baseDamage": 6,
        "ammoTonnagePerShot": 0.041666666666666664,
        "minRange": 180,
        "maxRange": 630,
        "indirectFire": true
      },
      ...
      {
        "name": "M Pulse++ (-4 Heat, +1 Acc)",
        "tonnage": 2,
        "heat": 16,
        "shots": 1,
        "baseDamage": 50,
        "ammoTonnagePerShot": 0.0,
        "minRange": 0,
        "maxRange": 270,
        "indirectFire": false
      },
      ...
    ]
    


    여기에 필요한 필드를 추측하는 중입니다. 일부 필드를 추가하거나 변경해야 할 수도 있습니다.

    소스 데이터가 JSON이기 때문에 모든 JSON을 수집하여 하나의 큰 JSON 배열에 넣고 사용할 수 있지만 이 패턴이 마음에 들지 않습니다.

    지금까지의 이야기



    All the code is on GitHub .

    다음에 온다



    다음 몇 개의 에피소드에서는 BATTLETECH 무기 데이터의 작은 Svelte 대화형 시각화를 만들 것입니다.

    좋은 웹페이지 즐겨찾기