hytale 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +1430 -15
- data/exe/hytale +497 -0
- data/lib/hytale/client/assets.rb +207 -0
- data/lib/hytale/client/block_type.rb +169 -0
- data/lib/hytale/client/config.rb +98 -0
- data/lib/hytale/client/cosmetics.rb +95 -0
- data/lib/hytale/client/item_type.rb +248 -0
- data/lib/hytale/client/launcher_log/launcher_log_entry.rb +58 -0
- data/lib/hytale/client/launcher_log/launcher_log_session.rb +57 -0
- data/lib/hytale/client/launcher_log.rb +99 -0
- data/lib/hytale/client/locale.rb +234 -0
- data/lib/hytale/client/map/block.rb +135 -0
- data/lib/hytale/client/map/chunk.rb +695 -0
- data/lib/hytale/client/map/marker.rb +50 -0
- data/lib/hytale/client/map/region.rb +278 -0
- data/lib/hytale/client/map/renderer.rb +435 -0
- data/lib/hytale/client/map.rb +271 -0
- data/lib/hytale/client/memories.rb +59 -0
- data/lib/hytale/client/npc_memory.rb +39 -0
- data/lib/hytale/client/permissions.rb +52 -0
- data/lib/hytale/client/player/entity_stats.rb +46 -0
- data/lib/hytale/client/player/inventory.rb +54 -0
- data/lib/hytale/client/player/item.rb +102 -0
- data/lib/hytale/client/player/item_storage.rb +40 -0
- data/lib/hytale/client/player/player_memory.rb +29 -0
- data/lib/hytale/client/player/position.rb +12 -0
- data/lib/hytale/client/player/rotation.rb +11 -0
- data/lib/hytale/client/player/vector3.rb +12 -0
- data/lib/hytale/client/player.rb +101 -0
- data/lib/hytale/client/player_skin.rb +179 -0
- data/lib/hytale/client/prefab/palette_entry.rb +49 -0
- data/lib/hytale/client/prefab.rb +184 -0
- data/lib/hytale/client/process.rb +57 -0
- data/lib/hytale/client/save/backup.rb +43 -0
- data/lib/hytale/client/save/server_log.rb +42 -0
- data/lib/hytale/client/save.rb +157 -0
- data/lib/hytale/client/settings/audio_settings.rb +30 -0
- data/lib/hytale/client/settings/builder_tools_settings.rb +27 -0
- data/lib/hytale/client/settings/gameplay_settings.rb +23 -0
- data/lib/hytale/client/settings/input_bindings.rb +25 -0
- data/lib/hytale/client/settings/mouse_settings.rb +23 -0
- data/lib/hytale/client/settings/rendering_settings.rb +30 -0
- data/lib/hytale/client/settings.rb +74 -0
- data/lib/hytale/client/world/client_effects.rb +24 -0
- data/lib/hytale/client/world/death_settings.rb +22 -0
- data/lib/hytale/client/world.rb +88 -0
- data/lib/hytale/client/zone/region.rb +52 -0
- data/lib/hytale/client/zone.rb +68 -0
- data/lib/hytale/client.rb +142 -0
- data/lib/hytale/server/process.rb +11 -0
- data/lib/hytale/server.rb +6 -0
- data/lib/hytale/version.rb +1 -1
- data/lib/hytale.rb +37 -2
- metadata +119 -10
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
# Represents a type of block (e.g., "Rock_Stone", "Soil_Grass")
|
|
6
|
+
# This is the definition/template of a block type with texture and category info.
|
|
7
|
+
# For positioned blocks in the world, see Block.
|
|
8
|
+
class BlockType
|
|
9
|
+
attr_reader :id
|
|
10
|
+
|
|
11
|
+
def initialize(id)
|
|
12
|
+
@id = id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def category
|
|
16
|
+
parts = id.split("_")
|
|
17
|
+
|
|
18
|
+
parts.first if parts.any?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def subcategory
|
|
22
|
+
parts = id.split("_")
|
|
23
|
+
|
|
24
|
+
parts[1] if parts.length > 2
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def name
|
|
28
|
+
id.tr("_", " ")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def state_definition?
|
|
32
|
+
id.start_with?("*")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def base_id
|
|
36
|
+
state_definition? ? id[1..] : id
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def texture_name
|
|
40
|
+
"#{base_id}.png"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def texture_path
|
|
44
|
+
find_texture_path || Assets.block_texture_path(texture_name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def texture_exists?
|
|
48
|
+
!!find_texture_path
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def texture_data
|
|
52
|
+
path = find_texture_path
|
|
53
|
+
|
|
54
|
+
return nil unless path
|
|
55
|
+
|
|
56
|
+
relative_path = path.sub("#{Assets.cache_path}/", "")
|
|
57
|
+
|
|
58
|
+
Assets.read(relative_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def find_texture_path
|
|
64
|
+
path = Assets.block_texture_path(texture_name)
|
|
65
|
+
|
|
66
|
+
return path if File.exist?(path)
|
|
67
|
+
|
|
68
|
+
variants = [
|
|
69
|
+
"#{base_id}_Sunny",
|
|
70
|
+
"#{base_id}_Deep",
|
|
71
|
+
"#{base_id}_Top",
|
|
72
|
+
"#{base_id}_Side",
|
|
73
|
+
base_id,
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
variants.each do |variant|
|
|
77
|
+
path = Assets.block_texture_path("#{variant}.png")
|
|
78
|
+
return path if File.exist?(path)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
public
|
|
85
|
+
|
|
86
|
+
def to_s
|
|
87
|
+
"BlockType: #{name}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def inspect
|
|
91
|
+
"#<Hytale::Client::BlockType id=#{id.inspect}>"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_h
|
|
95
|
+
{
|
|
96
|
+
id: id,
|
|
97
|
+
category: category,
|
|
98
|
+
name: name,
|
|
99
|
+
texture_path: texture_path,
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def ==(other)
|
|
104
|
+
other.is_a?(BlockType) && other.id == id
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def eql?(other)
|
|
108
|
+
self == other
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def hash
|
|
112
|
+
id.hash
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class << self
|
|
116
|
+
def all
|
|
117
|
+
@all ||= load_all_blocks
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def find(id)
|
|
121
|
+
all.find { |block| block.id == id }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def where(category: nil, subcategory: nil)
|
|
125
|
+
results = all
|
|
126
|
+
results = results.select { |b| b.category == category } if category
|
|
127
|
+
results = results.select { |b| b.subcategory == subcategory } if subcategory
|
|
128
|
+
|
|
129
|
+
results
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def categories
|
|
133
|
+
all.map(&:category).compact.uniq.sort
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def subcategories
|
|
137
|
+
all.map(&:subcategory).compact.uniq.sort
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def count
|
|
141
|
+
all.count
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def reload!
|
|
145
|
+
@all = nil
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def all_textures
|
|
149
|
+
Assets.block_textures
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
def load_all_blocks
|
|
155
|
+
texture_names = Assets.block_textures
|
|
156
|
+
|
|
157
|
+
block_ids = texture_names
|
|
158
|
+
.reject { |t| t.start_with?("T_") } # Helper textures (T_Crack_*, etc.)
|
|
159
|
+
.reject { |t| t.start_with?("_") } # Internal textures
|
|
160
|
+
.reject { |t| t.end_with?("_GS") } # Greyscale variants
|
|
161
|
+
.uniq
|
|
162
|
+
.sort
|
|
163
|
+
|
|
164
|
+
block_ids.map { |id| new(id) }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
module Config
|
|
6
|
+
class << self
|
|
7
|
+
def data_path
|
|
8
|
+
@data_path ||= default_data_path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_writer :data_path, :assets_cache_path
|
|
12
|
+
|
|
13
|
+
def reset!
|
|
14
|
+
@data_path = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def default_data_path
|
|
18
|
+
case RUBY_PLATFORM
|
|
19
|
+
when /darwin/
|
|
20
|
+
File.expand_path("~/Library/Application Support/Hytale")
|
|
21
|
+
when /mswin|mingw|cygwin/
|
|
22
|
+
File.join(ENV.fetch("APPDATA", ""), "Hytale")
|
|
23
|
+
when /linux/
|
|
24
|
+
File.expand_path("~/.local/share/Hytale")
|
|
25
|
+
else
|
|
26
|
+
raise Error, "Unsupported platform: #{RUBY_PLATFORM}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def user_data_path
|
|
31
|
+
File.join(data_path, "UserData")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def settings_path
|
|
35
|
+
File.join(user_data_path, "Settings.json")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def saves_path
|
|
39
|
+
File.join(user_data_path, "Saves")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def logs_path
|
|
43
|
+
File.join(user_data_path, "Logs")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def telemetry_path
|
|
47
|
+
File.join(user_data_path, "Telemetry")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def launcher_log_path
|
|
51
|
+
File.join(data_path, "hytale-launcher.log")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def install_path
|
|
55
|
+
File.join(data_path, "install")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def prefab_cache_path
|
|
59
|
+
File.join(user_data_path, "PrefabCache")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def player_skins_path
|
|
63
|
+
File.join(user_data_path, "CachedPlayerSkins")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def avatar_previews_path
|
|
67
|
+
File.join(user_data_path, "CachedAvatarPreviews")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def prefabs_path
|
|
71
|
+
pattern = File.join(prefab_cache_path, "*", "Server", "Prefabs")
|
|
72
|
+
dirs = Dir.glob(pattern)
|
|
73
|
+
|
|
74
|
+
dirs.first
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def assets_path
|
|
78
|
+
pattern = File.join(install_path, "release", "package", "game", "*", "Assets.zip")
|
|
79
|
+
files = Dir.glob(pattern)
|
|
80
|
+
|
|
81
|
+
files.first
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def assets_cache_path
|
|
85
|
+
@assets_cache_path ||= File.expand_path("assets", gem_root)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def gem_root
|
|
89
|
+
File.expand_path("../../..", __dir__)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def exists?
|
|
93
|
+
File.directory?(data_path)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
class Cosmetics
|
|
6
|
+
CATALOG_PATH = "Cosmetics/CharacterCreator"
|
|
7
|
+
|
|
8
|
+
CATALOG_FILES = {
|
|
9
|
+
body_characteristics: "BodyCharacteristics.json",
|
|
10
|
+
capes: "Capes.json",
|
|
11
|
+
ear_accessories: "EarAccessory.json",
|
|
12
|
+
ears: "Ears.json",
|
|
13
|
+
eyebrows: "Eyebrows.json",
|
|
14
|
+
eyes: "Eyes.json",
|
|
15
|
+
face_accessories: "FaceAccessory.json",
|
|
16
|
+
faces: "Faces.json",
|
|
17
|
+
facial_hair: "FacialHair.json",
|
|
18
|
+
gloves: "Gloves.json",
|
|
19
|
+
haircuts: "Haircuts.json",
|
|
20
|
+
head_accessories: "HeadAccessory.json",
|
|
21
|
+
mouths: "Mouths.json",
|
|
22
|
+
overpants: "Overpants.json",
|
|
23
|
+
overtops: "Overtops.json",
|
|
24
|
+
pants: "Pants.json",
|
|
25
|
+
shoes: "Shoes.json",
|
|
26
|
+
skin_features: "SkinFeatures.json",
|
|
27
|
+
undertops: "Undertops.json",
|
|
28
|
+
underwear: "Underwear.json",
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
def catalog(type)
|
|
33
|
+
@catalogs ||= {}
|
|
34
|
+
@catalogs[type] ||= load_catalog(type)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Handle format like "VikinManBun.BrownDark" - split off the color variant
|
|
38
|
+
def find(type, id)
|
|
39
|
+
base_id = id.to_s.split(".").first
|
|
40
|
+
|
|
41
|
+
catalog(type)&.find { |item| item["Id"] == base_id }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def texture_path(type, id)
|
|
45
|
+
item = find(type, id)
|
|
46
|
+
return nil unless item
|
|
47
|
+
|
|
48
|
+
variant_name = variant(id)
|
|
49
|
+
|
|
50
|
+
if variant_name && item["Textures"]
|
|
51
|
+
variant_data = item["Textures"][variant_name]
|
|
52
|
+
|
|
53
|
+
return Assets.cached_path("Common/#{variant_data["Texture"]}") if variant_data && variant_data["Texture"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
texture = item["GreyscaleTexture"] || item["Texture"]
|
|
57
|
+
|
|
58
|
+
return nil unless texture
|
|
59
|
+
|
|
60
|
+
Assets.cached_path("Common/#{texture}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def model_path(type, id)
|
|
64
|
+
item = find(type, id)
|
|
65
|
+
return nil unless item
|
|
66
|
+
|
|
67
|
+
model = item["Model"]
|
|
68
|
+
return nil unless model
|
|
69
|
+
|
|
70
|
+
Assets.cached_path("Common/#{model}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def variant(id)
|
|
74
|
+
parts = id.to_s.split(".")
|
|
75
|
+
|
|
76
|
+
parts.length > 1 ? parts[1..].join(".") : nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def load_catalog(type)
|
|
82
|
+
filename = CATALOG_FILES[type]
|
|
83
|
+
return nil unless filename
|
|
84
|
+
|
|
85
|
+
path = Assets.cached_path("#{CATALOG_PATH}/#{filename}")
|
|
86
|
+
return nil unless File.exist?(path)
|
|
87
|
+
|
|
88
|
+
JSON.parse(File.read(path))
|
|
89
|
+
rescue JSON::ParserError
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hytale
|
|
6
|
+
module Client
|
|
7
|
+
# Represents a type of item with its full definition from game assets
|
|
8
|
+
# This includes stats, recipes, quality, etc.
|
|
9
|
+
class ItemType
|
|
10
|
+
ITEMS_PATH = "Server/Item/Items"
|
|
11
|
+
ICONS_PATH = "Common/Icons/ItemsGenerated"
|
|
12
|
+
|
|
13
|
+
attr_reader :id
|
|
14
|
+
|
|
15
|
+
def initialize(id, data: nil)
|
|
16
|
+
@id = id
|
|
17
|
+
@data = data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def data
|
|
21
|
+
@data ||= load_definition
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name(locale: nil)
|
|
25
|
+
if locale
|
|
26
|
+
Locale.item_name(id, locale: locale) || id.tr("_", " ")
|
|
27
|
+
else
|
|
28
|
+
Locale.item_name(id) || id.tr("_", " ")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def description(locale: nil)
|
|
33
|
+
if locale
|
|
34
|
+
Locale.item_description(id, locale: locale)
|
|
35
|
+
else
|
|
36
|
+
Locale.item_description(id)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def category
|
|
41
|
+
parts = id.split("_")
|
|
42
|
+
|
|
43
|
+
parts.first if parts.any?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def subcategory
|
|
47
|
+
parts = id.split("_")
|
|
48
|
+
|
|
49
|
+
parts[1] if parts.length > 2
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parent
|
|
53
|
+
data&.dig("Parent")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def quality
|
|
57
|
+
data&.dig("Quality")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def item_level
|
|
61
|
+
data&.dig("ItemLevel")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def max_durability
|
|
65
|
+
data&.dig("MaxDurability")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def max_stack_size
|
|
69
|
+
data&.dig("MaxStackSize")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def recipe
|
|
73
|
+
data&.dig("Recipe")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def recipe_inputs
|
|
77
|
+
recipe&.dig("Input") || []
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def recipe_time
|
|
81
|
+
recipe&.dig("TimeSeconds")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def model_path
|
|
85
|
+
data&.dig("Model")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def texture_path
|
|
89
|
+
data&.dig("Texture")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def icon_relative_path
|
|
93
|
+
data&.dig("Icon")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def icon_path
|
|
97
|
+
@icon_path ||= find_icon_path
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def icon_exists?
|
|
101
|
+
path = icon_path
|
|
102
|
+
path && File.exist?(path)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def definition_exists?
|
|
106
|
+
!!find_definition_path
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_s
|
|
110
|
+
"ItemType: #{name}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def inspect
|
|
114
|
+
"#<Hytale::Client::ItemType id=#{id.inspect} quality=#{quality.inspect}>"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_h
|
|
118
|
+
{
|
|
119
|
+
id: id,
|
|
120
|
+
name: name,
|
|
121
|
+
category: category,
|
|
122
|
+
quality: quality,
|
|
123
|
+
item_level: item_level,
|
|
124
|
+
max_durability: max_durability,
|
|
125
|
+
icon_path: icon_path,
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def ==(other)
|
|
130
|
+
other.is_a?(ItemType) && other.id == id
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def eql?(other)
|
|
134
|
+
self == other
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def hash
|
|
138
|
+
id.hash
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class << self
|
|
142
|
+
def all
|
|
143
|
+
@all ||= load_all_items
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def find(id)
|
|
147
|
+
all.find { |item| item.id == id }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def where(category: nil, quality: nil)
|
|
151
|
+
results = all
|
|
152
|
+
|
|
153
|
+
results = results.select { |i| i.category == category } if category
|
|
154
|
+
results = results.select { |i| i.quality == quality } if quality
|
|
155
|
+
|
|
156
|
+
results
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def categories
|
|
160
|
+
all.map(&:category).compact.uniq.sort
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def qualities
|
|
164
|
+
all.map(&:quality).compact.uniq.sort
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def count
|
|
168
|
+
all.count
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def reload!
|
|
172
|
+
@all = nil
|
|
173
|
+
@definition_paths = nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def definition_paths
|
|
177
|
+
@definition_paths ||= Assets.list(ITEMS_PATH)
|
|
178
|
+
.select { |f| f.end_with?(".json") }
|
|
179
|
+
.to_h { |f| [File.basename(f, ".json"), f] }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def load_all_items
|
|
185
|
+
icon_ids = Assets.item_icons
|
|
186
|
+
definition_ids = definition_paths.keys
|
|
187
|
+
all_ids = (icon_ids + definition_ids).uniq.sort
|
|
188
|
+
|
|
189
|
+
all_ids.map { |id| new(id) }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def find_icon_path
|
|
196
|
+
path = Assets.item_icon_path(id)
|
|
197
|
+
|
|
198
|
+
return path if File.exist?(path)
|
|
199
|
+
|
|
200
|
+
variations = generate_icon_variations
|
|
201
|
+
|
|
202
|
+
variations.each do |variation|
|
|
203
|
+
path = Assets.item_icon_path(variation)
|
|
204
|
+
|
|
205
|
+
return path if File.exist?(path)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if id.start_with?("EditorTool_")
|
|
209
|
+
tool_name = id.sub("EditorTool_", "")
|
|
210
|
+
path = Assets.cached_path("Common/Icons/Items/EditorTools/#{tool_name}.png")
|
|
211
|
+
|
|
212
|
+
return path if File.exist?(path)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
nil
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def generate_icon_variations
|
|
219
|
+
variations = []
|
|
220
|
+
|
|
221
|
+
variations << id.gsub("Shortbow", "Bow") if id.include?("Shortbow")
|
|
222
|
+
variations << id.gsub("Longbow", "Bow") if id.include?("Longbow")
|
|
223
|
+
variations << id.gsub("Longsword", "Sword") if id.include?("Longsword")
|
|
224
|
+
|
|
225
|
+
variations
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def find_definition_path
|
|
229
|
+
self.class.definition_paths[id]
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def load_definition
|
|
233
|
+
relative_path = find_definition_path
|
|
234
|
+
|
|
235
|
+
return nil unless relative_path
|
|
236
|
+
|
|
237
|
+
Assets.extract(relative_path)
|
|
238
|
+
full_path = Assets.cached_path(relative_path)
|
|
239
|
+
|
|
240
|
+
return nil unless File.exist?(full_path)
|
|
241
|
+
|
|
242
|
+
JSON.parse(File.read(full_path))
|
|
243
|
+
rescue JSON::ParserError
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
class LauncherLog
|
|
6
|
+
class LauncherLogEntry
|
|
7
|
+
attr_reader :timestamp, :level, :message, :raw
|
|
8
|
+
|
|
9
|
+
LOG_PATTERN = /^time=(\S+)\s+level=(\S+)\s+msg="([^"]*)"(.*)$/
|
|
10
|
+
|
|
11
|
+
def initialize(timestamp:, level:, message:, raw:)
|
|
12
|
+
@timestamp = timestamp
|
|
13
|
+
@level = level
|
|
14
|
+
@message = message
|
|
15
|
+
@raw = raw
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def error? = level == "ERROR"
|
|
19
|
+
def warn? = level == "WARN"
|
|
20
|
+
def info? = level == "INFO"
|
|
21
|
+
def debug? = level == "DEBUG"
|
|
22
|
+
|
|
23
|
+
def attributes
|
|
24
|
+
attrs = {}
|
|
25
|
+
|
|
26
|
+
raw.scan(/(\w+)=(\S+|"[^"]*")/).each do |key, value|
|
|
27
|
+
next if ["time", "level", "msg"].include?(key)
|
|
28
|
+
|
|
29
|
+
attrs[key] = value.delete_prefix('"').delete_suffix('"')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attrs
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"[#{timestamp}] #{level}: #{message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def parse(line)
|
|
41
|
+
return nil unless (match = line.match(LOG_PATTERN))
|
|
42
|
+
|
|
43
|
+
timestamp = begin
|
|
44
|
+
Time.parse(match[1])
|
|
45
|
+
rescue StandardError
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
level = match[2]
|
|
50
|
+
message = match[3]
|
|
51
|
+
|
|
52
|
+
new(timestamp: timestamp, level: level, message: message, raw: line)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
class LauncherLog
|
|
6
|
+
class LauncherLogSession
|
|
7
|
+
attr_reader :started_at, :entries
|
|
8
|
+
|
|
9
|
+
def initialize(started_at)
|
|
10
|
+
@started_at = started_at
|
|
11
|
+
@entries = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_entry(entry)
|
|
15
|
+
@entries << entry
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def version
|
|
19
|
+
start_entry = entries.find { |e| e.message&.include?("starting hytale-launcher") }
|
|
20
|
+
|
|
21
|
+
start_entry&.attributes&.[]("version")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def profile_uuid
|
|
25
|
+
profile_entry = entries.find { |e| e.message&.include?("setting current profile") }
|
|
26
|
+
|
|
27
|
+
return nil unless profile_entry
|
|
28
|
+
|
|
29
|
+
profile_entry.message&.match(/to (\S+)/)&.[](1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def game_launched?
|
|
33
|
+
entries.any? { |e| e.message&.include?("starting game process") }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def errors
|
|
37
|
+
entries.select(&:error?)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def duration
|
|
41
|
+
return nil if entries.empty?
|
|
42
|
+
|
|
43
|
+
last_time = entries.last.timestamp
|
|
44
|
+
first_time = entries.first.timestamp
|
|
45
|
+
|
|
46
|
+
return nil unless last_time && first_time
|
|
47
|
+
|
|
48
|
+
last_time - first_time
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
"Session at #{started_at} (v#{version})"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|