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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +21 -0
  3. data/README.md +1430 -15
  4. data/exe/hytale +497 -0
  5. data/lib/hytale/client/assets.rb +207 -0
  6. data/lib/hytale/client/block_type.rb +169 -0
  7. data/lib/hytale/client/config.rb +98 -0
  8. data/lib/hytale/client/cosmetics.rb +95 -0
  9. data/lib/hytale/client/item_type.rb +248 -0
  10. data/lib/hytale/client/launcher_log/launcher_log_entry.rb +58 -0
  11. data/lib/hytale/client/launcher_log/launcher_log_session.rb +57 -0
  12. data/lib/hytale/client/launcher_log.rb +99 -0
  13. data/lib/hytale/client/locale.rb +234 -0
  14. data/lib/hytale/client/map/block.rb +135 -0
  15. data/lib/hytale/client/map/chunk.rb +695 -0
  16. data/lib/hytale/client/map/marker.rb +50 -0
  17. data/lib/hytale/client/map/region.rb +278 -0
  18. data/lib/hytale/client/map/renderer.rb +435 -0
  19. data/lib/hytale/client/map.rb +271 -0
  20. data/lib/hytale/client/memories.rb +59 -0
  21. data/lib/hytale/client/npc_memory.rb +39 -0
  22. data/lib/hytale/client/permissions.rb +52 -0
  23. data/lib/hytale/client/player/entity_stats.rb +46 -0
  24. data/lib/hytale/client/player/inventory.rb +54 -0
  25. data/lib/hytale/client/player/item.rb +102 -0
  26. data/lib/hytale/client/player/item_storage.rb +40 -0
  27. data/lib/hytale/client/player/player_memory.rb +29 -0
  28. data/lib/hytale/client/player/position.rb +12 -0
  29. data/lib/hytale/client/player/rotation.rb +11 -0
  30. data/lib/hytale/client/player/vector3.rb +12 -0
  31. data/lib/hytale/client/player.rb +101 -0
  32. data/lib/hytale/client/player_skin.rb +179 -0
  33. data/lib/hytale/client/prefab/palette_entry.rb +49 -0
  34. data/lib/hytale/client/prefab.rb +184 -0
  35. data/lib/hytale/client/process.rb +57 -0
  36. data/lib/hytale/client/save/backup.rb +43 -0
  37. data/lib/hytale/client/save/server_log.rb +42 -0
  38. data/lib/hytale/client/save.rb +157 -0
  39. data/lib/hytale/client/settings/audio_settings.rb +30 -0
  40. data/lib/hytale/client/settings/builder_tools_settings.rb +27 -0
  41. data/lib/hytale/client/settings/gameplay_settings.rb +23 -0
  42. data/lib/hytale/client/settings/input_bindings.rb +25 -0
  43. data/lib/hytale/client/settings/mouse_settings.rb +23 -0
  44. data/lib/hytale/client/settings/rendering_settings.rb +30 -0
  45. data/lib/hytale/client/settings.rb +74 -0
  46. data/lib/hytale/client/world/client_effects.rb +24 -0
  47. data/lib/hytale/client/world/death_settings.rb +22 -0
  48. data/lib/hytale/client/world.rb +88 -0
  49. data/lib/hytale/client/zone/region.rb +52 -0
  50. data/lib/hytale/client/zone.rb +68 -0
  51. data/lib/hytale/client.rb +142 -0
  52. data/lib/hytale/server/process.rb +11 -0
  53. data/lib/hytale/server.rb +6 -0
  54. data/lib/hytale/version.rb +1 -1
  55. data/lib/hytale.rb +37 -2
  56. 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