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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
class LauncherLog
|
|
6
|
+
attr_reader :path
|
|
7
|
+
|
|
8
|
+
def initialize(path)
|
|
9
|
+
@path = path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def content
|
|
13
|
+
@content ||= File.read(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lines
|
|
17
|
+
@lines ||= content.lines.map(&:chomp)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def entries
|
|
21
|
+
@entries ||= lines.filter_map { |line| LauncherLogEntry.parse(line) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def errors
|
|
25
|
+
entries.select(&:error?)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def warnings
|
|
29
|
+
entries.select(&:warn?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def info
|
|
33
|
+
entries.select(&:info?)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def game_launches
|
|
37
|
+
entries.select { |e| e.message&.include?("starting game process") }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def updates
|
|
41
|
+
entries.select { |e| e.message&.include?("applying update") }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_version
|
|
45
|
+
version_entry = entries.reverse.find { |e| e.message&.include?("starting hytale-launcher") }
|
|
46
|
+
version_entry&.attributes&.[]("version")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current_profile_uuid
|
|
50
|
+
profile_entry = entries.reverse.find { |e| e.message&.include?("setting current profile") }
|
|
51
|
+
|
|
52
|
+
return nil unless profile_entry
|
|
53
|
+
|
|
54
|
+
profile_entry.message&.match(/to (\S+)/)&.[](1)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def current_channel
|
|
58
|
+
channel_entry = entries.reverse.find { |e| e.message&.include?("setting channel") }
|
|
59
|
+
|
|
60
|
+
return nil unless channel_entry
|
|
61
|
+
|
|
62
|
+
channel_entry.attributes&.[]("channel")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def last_game_launch
|
|
66
|
+
game_launches.last
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def sessions
|
|
70
|
+
sessions = []
|
|
71
|
+
current_session = nil
|
|
72
|
+
|
|
73
|
+
entries.each do |entry|
|
|
74
|
+
if entry.message&.include?("starting hytale-launcher")
|
|
75
|
+
current_session = LauncherLogSession.new(entry.timestamp)
|
|
76
|
+
sessions << current_session
|
|
77
|
+
end
|
|
78
|
+
current_session&.add_entry(entry)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sessions
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def each(&)
|
|
85
|
+
entries.each(&)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
include Enumerable
|
|
89
|
+
|
|
90
|
+
class << self
|
|
91
|
+
def load(path: Config.launcher_log_path)
|
|
92
|
+
raise NotFoundError, "Launcher log not found: #{path}" unless File.exist?(path)
|
|
93
|
+
|
|
94
|
+
new(path)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
# Provides localized strings from game language files
|
|
6
|
+
# Supports all available game languages
|
|
7
|
+
class Locale
|
|
8
|
+
DEFAULT_LOCALE = "en-US"
|
|
9
|
+
|
|
10
|
+
attr_reader :code, :language, :region
|
|
11
|
+
|
|
12
|
+
def initialize(code)
|
|
13
|
+
@code = code
|
|
14
|
+
@language, @region = code.split("-")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def translations
|
|
18
|
+
self.class.translations(code)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def t(key, fallback: true)
|
|
22
|
+
self.class.t(key, locale: code, fallback: fallback)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias translate t
|
|
26
|
+
|
|
27
|
+
def zone_name(zone_id)
|
|
28
|
+
self.class.zone_name(zone_id, locale: code)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def region_name(region_id)
|
|
32
|
+
self.class.region_name(region_id, locale: code)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def item_name(item_id)
|
|
36
|
+
self.class.item_name(item_id, locale: code)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def item_description(item_id)
|
|
40
|
+
self.class.item_description(item_id, locale: code)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def zones
|
|
44
|
+
self.class.zones(locale: code)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def regions
|
|
48
|
+
self.class.regions(locale: code)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
code
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inspect
|
|
56
|
+
"#<Hytale::Client::Locale code=#{code.inspect}>"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class << self
|
|
60
|
+
def available
|
|
61
|
+
@available ||= detect_available_locales
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def default
|
|
65
|
+
DEFAULT_LOCALE
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def find(code)
|
|
69
|
+
return nil unless available.include?(code)
|
|
70
|
+
|
|
71
|
+
new(code)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def all
|
|
75
|
+
available.map { |code| new(code) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get a translation by key
|
|
79
|
+
# @param key [String] The translation key (e.g., "map.region.Zone1_Tier1")
|
|
80
|
+
# @param locale [String] The locale to use (default: en-US)
|
|
81
|
+
# @param fallback [Boolean] If true, fall back to English when translation missing
|
|
82
|
+
# @return [String, nil] The translated string or nil if not found
|
|
83
|
+
def t(key, locale: DEFAULT_LOCALE, fallback: true)
|
|
84
|
+
result = translations(locale)[key]
|
|
85
|
+
|
|
86
|
+
# Fall back to English if translation missing and fallback enabled
|
|
87
|
+
result = translations(DEFAULT_LOCALE)[key] if result.nil? && fallback && locale != DEFAULT_LOCALE
|
|
88
|
+
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
alias translate t
|
|
93
|
+
|
|
94
|
+
# Get all translations for a locale
|
|
95
|
+
# @param locale [String] The locale (default: en-US)
|
|
96
|
+
# @return [Hash] Key-value hash of all translations
|
|
97
|
+
def translations(locale = DEFAULT_LOCALE)
|
|
98
|
+
@translations ||= {}
|
|
99
|
+
@translations[locale] ||= load_translations(locale)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get zone display name
|
|
103
|
+
# @param zone_id [String] The zone ID (e.g., "Emerald_Wilds")
|
|
104
|
+
# @param locale [String] The locale (default: en-US)
|
|
105
|
+
def zone_name(zone_id, locale: DEFAULT_LOCALE)
|
|
106
|
+
t("map.zone.#{zone_id}", locale: locale)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get region display name
|
|
110
|
+
# @param region_id [String] The region ID (e.g., "Zone1_Tier1")
|
|
111
|
+
# @param locale [String] The locale (default: en-US)
|
|
112
|
+
def region_name(region_id, locale: DEFAULT_LOCALE)
|
|
113
|
+
t("map.region.#{region_id}", locale: locale)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get item display name
|
|
117
|
+
# @param item_id [String] The item ID (e.g., "Weapon_Sword_Copper")
|
|
118
|
+
# @param locale [String] The locale (default: en-US)
|
|
119
|
+
def item_name(item_id, locale: DEFAULT_LOCALE)
|
|
120
|
+
t("items.#{item_id}.name", locale: locale)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Get item description
|
|
124
|
+
# @param item_id [String] The item ID
|
|
125
|
+
# @param locale [String] The locale (default: en-US)
|
|
126
|
+
def item_description(item_id, locale: DEFAULT_LOCALE)
|
|
127
|
+
t("items.#{item_id}.description", locale: locale)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get all zone names
|
|
131
|
+
# @param locale [String] The locale (default: en-US)
|
|
132
|
+
# @return [Hash] zone_id => display_name
|
|
133
|
+
def zones(locale: DEFAULT_LOCALE)
|
|
134
|
+
prefix = "map.zone."
|
|
135
|
+
translations(locale)
|
|
136
|
+
.select { |k, _| k.start_with?(prefix) }
|
|
137
|
+
.transform_keys { |k| k.delete_prefix(prefix) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get all region names
|
|
141
|
+
# @param locale [String] The locale (default: en-US)
|
|
142
|
+
# @return [Hash] region_id => display_name
|
|
143
|
+
def regions(locale: DEFAULT_LOCALE)
|
|
144
|
+
prefix = "map.region."
|
|
145
|
+
translations(locale)
|
|
146
|
+
.select { |k, _| k.start_with?(prefix) }
|
|
147
|
+
.transform_keys { |k| k.delete_prefix(prefix) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Search translations by key pattern
|
|
151
|
+
# @param pattern [Regexp, String] Pattern to match against keys
|
|
152
|
+
# @param locale [String] The locale (default: en-US)
|
|
153
|
+
# @return [Hash] Matching key-value pairs
|
|
154
|
+
def search(pattern, locale: DEFAULT_LOCALE)
|
|
155
|
+
pattern = Regexp.new(pattern) if pattern.is_a?(String)
|
|
156
|
+
translations(locale).select { |k, _| k.match?(pattern) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Clear cached translations and available locales
|
|
160
|
+
def reload!
|
|
161
|
+
@translations = nil
|
|
162
|
+
@available = nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def detect_available_locales
|
|
168
|
+
Assets.list("Server/Languages")
|
|
169
|
+
.map { |f| f.split("/")[2] }
|
|
170
|
+
.uniq
|
|
171
|
+
.reject { |l| l.nil? || l.end_with?(".lang") }
|
|
172
|
+
.sort
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def load_translations(locale)
|
|
176
|
+
result = {}
|
|
177
|
+
|
|
178
|
+
server_lang = load_lang_file("Server/Languages/#{locale}/server.lang")
|
|
179
|
+
result.merge!(server_lang) if server_lang
|
|
180
|
+
|
|
181
|
+
result
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def load_lang_file(path)
|
|
185
|
+
return nil unless Assets.extract(path)
|
|
186
|
+
|
|
187
|
+
full_path = Assets.cached_path(path)
|
|
188
|
+
|
|
189
|
+
return nil unless File.exist?(full_path)
|
|
190
|
+
|
|
191
|
+
parse_lang_file(File.read(full_path))
|
|
192
|
+
rescue StandardError
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def parse_lang_file(content)
|
|
197
|
+
result = {}
|
|
198
|
+
current_key = nil
|
|
199
|
+
current_value = []
|
|
200
|
+
in_multiline = false
|
|
201
|
+
|
|
202
|
+
content.each_line do |line|
|
|
203
|
+
line = line.chomp
|
|
204
|
+
|
|
205
|
+
if in_multiline
|
|
206
|
+
if line.end_with?("\\")
|
|
207
|
+
current_value << line[0..-2]
|
|
208
|
+
else
|
|
209
|
+
current_value << line
|
|
210
|
+
result[current_key] = current_value.join("\n")
|
|
211
|
+
in_multiline = false
|
|
212
|
+
current_key = nil
|
|
213
|
+
current_value = []
|
|
214
|
+
end
|
|
215
|
+
elsif line =~ /^([a-zA-Z0-9_.]+)\s*=\s*(.*)$/
|
|
216
|
+
key = ::Regexp.last_match(1)
|
|
217
|
+
value = ::Regexp.last_match(2)
|
|
218
|
+
|
|
219
|
+
if value.end_with?("\\")
|
|
220
|
+
current_key = key
|
|
221
|
+
current_value = [value[0..-2]]
|
|
222
|
+
in_multiline = true
|
|
223
|
+
else
|
|
224
|
+
result[key] = value
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
result
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hytale
|
|
4
|
+
module Client
|
|
5
|
+
class Map
|
|
6
|
+
# Represents a specific block at coordinates in the world.
|
|
7
|
+
# Contains a reference to its BlockType for texture/category info.
|
|
8
|
+
class Block
|
|
9
|
+
attr_reader :block_type, :x, :y, :z, :chunk
|
|
10
|
+
|
|
11
|
+
# @param block_type [BlockType] The type of this block
|
|
12
|
+
# @param x [Integer] Local X coordinate within chunk (0-15)
|
|
13
|
+
# @param y [Integer] Y coordinate (height)
|
|
14
|
+
# @param z [Integer] Local Z coordinate within chunk (0-15)
|
|
15
|
+
# @param chunk [Chunk, nil] The chunk this block belongs to
|
|
16
|
+
def initialize(block_type, x:, y:, z:, chunk: nil)
|
|
17
|
+
@block_type = block_type
|
|
18
|
+
@x = x
|
|
19
|
+
@y = y
|
|
20
|
+
@z = z
|
|
21
|
+
@chunk = chunk
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def id
|
|
25
|
+
block_type.id
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name
|
|
29
|
+
block_type.name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def category
|
|
33
|
+
block_type.category
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def subcategory
|
|
37
|
+
block_type.subcategory
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def texture_path
|
|
41
|
+
block_type.texture_path
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def texture_exists?
|
|
45
|
+
block_type.texture_exists?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def texture_data
|
|
49
|
+
block_type.texture_data
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def world_x
|
|
53
|
+
return nil unless chunk&.world_x
|
|
54
|
+
|
|
55
|
+
chunk.world_x + x
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def world_z
|
|
59
|
+
return nil unless chunk&.world_z
|
|
60
|
+
|
|
61
|
+
chunk.world_z + z
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def world_y
|
|
65
|
+
y
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def world_position
|
|
69
|
+
return nil unless world_x && world_z
|
|
70
|
+
|
|
71
|
+
[world_x, world_y, world_z]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def local_position
|
|
75
|
+
[x, y, z]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def empty?
|
|
79
|
+
id == "Empty" || id.start_with?("Air")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def solid?
|
|
83
|
+
!empty? && !liquid?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def liquid?
|
|
87
|
+
["Water", "Lava"].include?(category)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def vegetation?
|
|
91
|
+
category == "Plant" || id.include?("Grass")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_s
|
|
95
|
+
position = world_position ? "(#{world_x}, #{world_y}, #{world_z})" : "(#{x}, #{y}, #{z})"
|
|
96
|
+
|
|
97
|
+
"Block: #{name} at #{position}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def inspect
|
|
101
|
+
"#<Hytale::Client::Map::Block id=#{id.inspect} x=#{x} y=#{y} z=#{z}>"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_h
|
|
105
|
+
{
|
|
106
|
+
id: id,
|
|
107
|
+
name: name,
|
|
108
|
+
category: category,
|
|
109
|
+
x: x,
|
|
110
|
+
y: y,
|
|
111
|
+
z: z,
|
|
112
|
+
world_x: world_x,
|
|
113
|
+
world_y: world_y,
|
|
114
|
+
world_z: world_z,
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def ==(other)
|
|
119
|
+
return false unless other.is_a?(Block)
|
|
120
|
+
|
|
121
|
+
other.id == id && other.x == x && other.y == y && other.z == z &&
|
|
122
|
+
other.chunk == chunk
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def eql?(other)
|
|
126
|
+
self == other
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def hash
|
|
130
|
+
[id, x, y, z, chunk&.object_id].hash
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|