rgame 0.1.0 → 0.3.0

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -0
  3. data/README.md +142 -243
  4. data/docs/api/README.md +141 -90
  5. data/docs/api/app.md +125 -68
  6. data/docs/api/assets.md +203 -166
  7. data/docs/api/audio.md +130 -89
  8. data/docs/api/cli.md +259 -0
  9. data/docs/api/components.md +1045 -254
  10. data/docs/api/drawing.md +187 -119
  11. data/docs/api/examples.md +263 -0
  12. data/docs/api/game.md +165 -45
  13. data/docs/api/images.md +39 -39
  14. data/docs/api/input.md +344 -85
  15. data/docs/api/internals.md +241 -62
  16. data/docs/api/localization.md +285 -0
  17. data/docs/api/scene_graph.md +503 -123
  18. data/docs/api/signals.md +81 -76
  19. data/docs/api/systems.md +197 -64
  20. data/docs/api/text.md +58 -41
  21. data/docs/api/tile_maps.md +237 -0
  22. data/docs/api/toolbox.md +400 -147
  23. data/docs/api/ui.md +927 -0
  24. data/docs/api/values.md +234 -19
  25. data/examples/assets/README.md +322 -0
  26. data/examples/assets/blip.ogg +0 -0
  27. data/examples/assets/glyphs.json +5 -0
  28. data/examples/assets/glyphs.png +0 -0
  29. data/examples/assets/hero.json +12 -0
  30. data/examples/assets/hero.png +0 -0
  31. data/examples/assets/icons.json +13 -0
  32. data/examples/assets/icons.png +0 -0
  33. data/examples/assets/music.ogg +0 -0
  34. data/examples/assets/skills.json +10 -0
  35. data/examples/assets/skills.png +0 -0
  36. data/examples/assets/tileset.png +0 -0
  37. data/examples/assets/tileset.tsx +65 -0
  38. data/examples/assets/town.tmx +26 -0
  39. data/examples/assets/ui.json +11 -0
  40. data/examples/assets/ui.png +0 -0
  41. data/examples/collision/locales/en.yml +8 -0
  42. data/examples/collision/main.rb +316 -0
  43. data/examples/collision_tiles/locales/en.yml +9 -0
  44. data/examples/collision_tiles/main.rb +274 -0
  45. data/examples/fullscreen/locales/en.yml +10 -0
  46. data/examples/fullscreen/main.rb +216 -0
  47. data/examples/game_menu/locales/en.yml +8 -0
  48. data/examples/game_menu/main.rb +170 -0
  49. data/examples/input_glyphs/locales/en.yml +14 -0
  50. data/examples/input_glyphs/main.rb +213 -0
  51. data/examples/jump_topdown/locales/en.yml +9 -0
  52. data/examples/jump_topdown/main.rb +178 -0
  53. data/examples/localization/locales/de.yml +12 -0
  54. data/examples/localization/locales/en.yml +13 -0
  55. data/examples/localization/main.rb +158 -0
  56. data/examples/menu_navigation/locales/en.yml +23 -0
  57. data/examples/menu_navigation/main.rb +365 -0
  58. data/examples/music/locales/en.yml +7 -0
  59. data/examples/music/main.rb +134 -0
  60. data/examples/pathfinding/locales/en.yml +17 -0
  61. data/examples/pathfinding/main.rb +298 -0
  62. data/examples/pooling/locales/en.yml +7 -0
  63. data/examples/pooling/main.rb +259 -0
  64. data/examples/quick_wheel/locales/en.yml +16 -0
  65. data/examples/quick_wheel/main.rb +184 -0
  66. data/examples/radial_menu/locales/en.yml +16 -0
  67. data/examples/radial_menu/main.rb +184 -0
  68. data/examples/save_load/locales/en.yml +11 -0
  69. data/examples/save_load/main.rb +207 -0
  70. data/examples/save_load_ids/locales/en.yml +11 -0
  71. data/examples/save_load_ids/main.rb +322 -0
  72. data/examples/scroll_map/locales/en.yml +4 -0
  73. data/examples/scroll_map/main.rb +140 -0
  74. data/examples/signals/locales/en.yml +6 -0
  75. data/examples/signals/main.rb +278 -0
  76. data/examples/skill_bar/locales/en.yml +14 -0
  77. data/examples/skill_bar/main.rb +159 -0
  78. data/examples/sound/locales/en.yml +6 -0
  79. data/examples/sound/main.rb +122 -0
  80. data/examples/split_screen/locales/en.yml +9 -0
  81. data/examples/split_screen/main.rb +304 -0
  82. data/examples/sprite/locales/en.yml +8 -0
  83. data/examples/sprite/main.rb +180 -0
  84. data/examples/timer/locales/en.yml +12 -0
  85. data/examples/timer/main.rb +273 -0
  86. data/examples/velocity/locales/en.yml +6 -0
  87. data/examples/velocity/main.rb +196 -0
  88. data/examples/walk/locales/en.yml +4 -0
  89. data/examples/walk/main.rb +99 -0
  90. data/exe/rgame +9 -0
  91. data/ext/README.md +6 -5
  92. data/ext/rgame_core/app/app.c +215 -11
  93. data/ext/rgame_core/app/locale.c +67 -0
  94. data/ext/rgame_core/app/locale.h +28 -0
  95. data/ext/rgame_core/audio/audio.c +113 -2
  96. data/ext/rgame_core/example.rb +18 -56
  97. data/ext/rgame_core/extconf.rb +16 -113
  98. data/ext/rgame_core/graphics/canvas.c +45 -4
  99. data/ext/rgame_core/graphics/canvas.h +65 -10
  100. data/ext/rgame_core/graphics/clip.c +22 -13
  101. data/ext/rgame_core/include/rgame/core.h +151 -5
  102. data/ext/rgame_core/input/gamepad.c +57 -3
  103. data/ext/rgame_core/ruby/audio_ext.c +10 -5
  104. data/ext/rgame_core/ruby/core_ext.c +46 -7
  105. data/ext/rgame_core/ruby/core_ext.h +3 -0
  106. data/ext/rgame_core/ruby/locale_ext.c +44 -0
  107. data/ext/rgame_core/ruby/recording_ext.c +1 -1
  108. data/ext/rgame_core/ruby/renderer_ext.c +42 -19
  109. data/ext/rgame_util/color_ext.c +12 -3
  110. data/ext/rgame_util/extconf.rb +2 -20
  111. data/ext/rgame_util/route_search.c +305 -0
  112. data/ext/rgame_util/route_search.h +86 -0
  113. data/ext/rgame_util/route_search_ext.c +150 -0
  114. data/ext/rgame_util/solid_grid.c +58 -0
  115. data/ext/rgame_util/solid_grid.h +49 -0
  116. data/ext/rgame_util/solid_grid_ext.c +161 -0
  117. data/ext/rgame_util/tile_sweep.c +164 -0
  118. data/ext/rgame_util/tile_sweep.h +62 -0
  119. data/ext/rgame_util/tile_sweep_ext.c +155 -0
  120. data/ext/rgame_util/util_ext.c +3 -0
  121. data/ext/rgame_util/util_ext.h +15 -0
  122. data/lib/rgame/boot.rb +0 -10
  123. data/lib/rgame/cli/new_project.rb +139 -0
  124. data/lib/rgame/cli/templates/Gemfile.tt +23 -0
  125. data/lib/rgame/cli/templates/README.md.tt +93 -0
  126. data/lib/rgame/cli/templates/Rakefile.tt +9 -0
  127. data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
  128. data/lib/rgame/cli/templates/game.rb.tt +23 -0
  129. data/lib/rgame/cli/templates/gitignore.tt +12 -0
  130. data/lib/rgame/cli/templates/main.rb.tt +11 -0
  131. data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
  132. data/lib/rgame/cli/templates/rspec.tt +2 -0
  133. data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
  134. data/lib/rgame/cli/templates/ruby-version.tt +1 -0
  135. data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
  136. data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
  137. data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
  138. data/lib/rgame/cli.rb +66 -0
  139. data/lib/rgame/core/app.rb +6 -42
  140. data/lib/rgame/core/asset_manager.rb +13 -31
  141. data/lib/rgame/core/audio.rb +37 -16
  142. data/lib/rgame/core/font.rb +0 -3
  143. data/lib/rgame/core/input.rb +35 -41
  144. data/lib/rgame/core/locale.rb +22 -0
  145. data/lib/rgame/core/nine_slice.rb +0 -21
  146. data/lib/rgame/core/recording.rb +3 -1
  147. data/lib/rgame/core/renderer.rb +75 -84
  148. data/lib/rgame/core/sprite_sheet.rb +0 -3
  149. data/lib/rgame/core/tile_map_renderer.rb +77 -65
  150. data/lib/rgame/core/ui_atlas.rb +28 -13
  151. data/lib/rgame/core.rb +1 -8
  152. data/lib/rgame/engine/actor_blockers.rb +131 -0
  153. data/lib/rgame/engine/animation_set.rb +1 -0
  154. data/lib/rgame/engine/audio_director.rb +36 -6
  155. data/lib/rgame/engine/bounds_blockers.rb +74 -0
  156. data/lib/rgame/engine/camera.rb +55 -10
  157. data/lib/rgame/engine/circle_collider.rb +4 -2
  158. data/lib/rgame/engine/collision_box.rb +26 -1
  159. data/lib/rgame/engine/collision_system.rb +110 -22
  160. data/lib/rgame/engine/component.rb +35 -1
  161. data/lib/rgame/engine/components/action_trigger.rb +0 -1
  162. data/lib/rgame/engine/components/animated_sprite.rb +31 -23
  163. data/lib/rgame/engine/components/box_collider.rb +99 -0
  164. data/lib/rgame/engine/components/camera_follow.rb +45 -0
  165. data/lib/rgame/engine/components/character_body.rb +21 -41
  166. data/lib/rgame/engine/components/circle_collider.rb +47 -11
  167. data/lib/rgame/engine/components/collision_world.rb +159 -31
  168. data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
  169. data/lib/rgame/engine/components/feet_collider.rb +61 -0
  170. data/lib/rgame/engine/components/hop.rb +76 -0
  171. data/lib/rgame/engine/components/identity.rb +73 -0
  172. data/lib/rgame/engine/components/mover.rb +285 -0
  173. data/lib/rgame/engine/components/navigator.rb +145 -0
  174. data/lib/rgame/engine/components/path_follow.rb +123 -31
  175. data/lib/rgame/engine/components/player_controller.rb +5 -2
  176. data/lib/rgame/engine/components/pool.rb +1 -1
  177. data/lib/rgame/engine/components/screen_wrap.rb +33 -11
  178. data/lib/rgame/engine/components/sprite.rb +22 -6
  179. data/lib/rgame/engine/components/targeting.rb +9 -11
  180. data/lib/rgame/engine/components/thrust_controller.rb +1 -1
  181. data/lib/rgame/engine/components/tile_world.rb +70 -29
  182. data/lib/rgame/engine/components/timer.rb +1 -1
  183. data/lib/rgame/engine/components/velocity.rb +23 -7
  184. data/lib/rgame/engine/components/wander_controller.rb +6 -2
  185. data/lib/rgame/engine/components/world.rb +133 -0
  186. data/lib/rgame/engine/contact_set.rb +74 -0
  187. data/lib/rgame/engine/culling.rb +45 -0
  188. data/lib/rgame/engine/debug_overlay.rb +23 -18
  189. data/lib/rgame/engine/i18n/plural.rb +45 -0
  190. data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
  191. data/lib/rgame/engine/i18n/template.rb +59 -0
  192. data/lib/rgame/engine/i18n.rb +276 -51
  193. data/lib/rgame/engine/input/action_mapper.rb +76 -22
  194. data/lib/rgame/engine/input/actions.rb +63 -12
  195. data/lib/rgame/engine/input/input_map.rb +196 -0
  196. data/lib/rgame/engine/layout.rb +82 -0
  197. data/lib/rgame/engine/nav_grid.rb +87 -0
  198. data/lib/rgame/engine/node2d.rb +364 -80
  199. data/lib/rgame/engine/path.rb +4 -6
  200. data/lib/rgame/engine/player.rb +69 -0
  201. data/lib/rgame/engine/player_layer.rb +70 -0
  202. data/lib/rgame/engine/players.rb +205 -0
  203. data/lib/rgame/engine/presentation.rb +171 -0
  204. data/lib/rgame/engine/scene/scene_stack.rb +29 -7
  205. data/lib/rgame/engine/sealed_privates.rb +54 -0
  206. data/lib/rgame/engine/spatial_hash.rb +53 -8
  207. data/lib/rgame/engine/text.rb +194 -0
  208. data/lib/rgame/engine/tile_blockers.rb +63 -0
  209. data/lib/rgame/engine/tile_map.rb +2 -3
  210. data/lib/rgame/engine/tile_map_layer.rb +82 -0
  211. data/lib/rgame/engine/tileset.rb +2 -4
  212. data/lib/rgame/engine/timer.rb +2 -2
  213. data/lib/rgame/engine/ui/button.rb +248 -0
  214. data/lib/rgame/engine/ui/column.rb +20 -0
  215. data/lib/rgame/engine/ui/icon_button.rb +93 -0
  216. data/lib/rgame/engine/ui/menu.rb +290 -0
  217. data/lib/rgame/engine/ui/navigation.rb +57 -0
  218. data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
  219. data/lib/rgame/engine/ui/option_button.rb +163 -0
  220. data/lib/rgame/engine/ui/panel_button.rb +32 -0
  221. data/lib/rgame/engine/ui/panel_menu.rb +36 -0
  222. data/lib/rgame/engine/ui/pointing.rb +146 -0
  223. data/lib/rgame/engine/ui/radial_menu.rb +85 -0
  224. data/lib/rgame/engine/ui/ring.rb +55 -0
  225. data/lib/rgame/engine/ui/row.rb +21 -0
  226. data/lib/rgame/engine/ui/shape_style.rb +102 -0
  227. data/lib/rgame/engine/ui/stack.rb +58 -0
  228. data/lib/rgame/engine/ui/stepping.rb +93 -0
  229. data/lib/rgame/engine/ui/text_button.rb +59 -0
  230. data/lib/rgame/engine/view.rb +76 -0
  231. data/lib/rgame/engine/viewports.rb +171 -0
  232. data/lib/rgame/engine/world_view.rb +71 -0
  233. data/lib/rgame/engine.rb +43 -25
  234. data/lib/rgame/game.rb +164 -22
  235. data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
  236. data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
  237. data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
  238. data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
  239. data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
  240. data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
  241. data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
  242. data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
  243. data/lib/rgame/rubocop/default.yml +39 -0
  244. data/lib/rgame/rubocop/plugin.rb +45 -0
  245. data/lib/rgame/rubocop.rb +11 -0
  246. data/lib/rgame/util/color.rb +20 -24
  247. data/lib/rgame/util/controls.rb +106 -44
  248. data/lib/rgame/util/route_search.rb +27 -0
  249. data/lib/rgame/util/save_file.rb +107 -0
  250. data/lib/rgame/util/solid_grid.rb +37 -0
  251. data/lib/rgame/util/tensor.rb +0 -9
  252. data/lib/rgame/util/tile_sweep.rb +36 -0
  253. data/lib/rgame/util/z.rb +123 -0
  254. data/lib/rgame/util.rb +5 -3
  255. data/lib/rgame/version.rb +1 -1
  256. data/lib/rgame.rb +0 -15
  257. metadata +176 -20
  258. data/lib/rgame/engine/actor.rb +0 -53
  259. data/lib/rgame/engine/body.rb +0 -49
  260. data/lib/rgame/engine/cached_label.rb +0 -33
  261. data/lib/rgame/engine/camera_view.rb +0 -28
  262. data/lib/rgame/engine/input/player_controller.rb +0 -14
  263. data/lib/rgame/engine/matrix.rb +0 -32
  264. data/lib/rgame/engine/resettable.rb +0 -67
  265. data/lib/rgame/engine/tile_collision.rb +0 -78
@@ -19,8 +19,19 @@ extern "C" {
19
19
 
20
20
  typedef struct rgame_app rgame_app;
21
21
 
22
- /* Creates the window, GL context and internal state. Returns NULL on failure. */
23
- rgame_app *rgame_app_create(int width, int height, const char *title);
22
+ /*
23
+ * Creates the window, GL context and internal state. Returns NULL on failure.
24
+ *
25
+ * `fullscreen` non-zero opens the window fullscreen straight away, rather than
26
+ * opening it windowed and switching after. The difference is visible: a switch
27
+ * made after creation shows one windowed frame first, which is the flash a
28
+ * player sees when a game starts up wrong.
29
+ *
30
+ * `width` and `height` still matter when starting fullscreen — they are the
31
+ * size the window returns to when it leaves fullscreen, and the size SDL keeps
32
+ * for it in the meantime.
33
+ */
34
+ rgame_app *rgame_app_create(int width, int height, const char *title, int fullscreen);
24
35
 
25
36
  /* Destroys the GL context/window and frees the app. Safe to call with NULL. */
26
37
  void rgame_app_destroy(rgame_app *app);
@@ -52,16 +63,93 @@ void rgame_app_destroy(rgame_app *app);
52
63
  #define RGAME_BUTTON_GAMEPAD_FIRST 0x1000
53
64
  #define RGAME_BUTTON_GAMEPAD_LAST 0x10FF
54
65
 
55
- /* Keyboard ids == SDL scancodes. Only the keys the engine actually binds are
56
- * named; adding one is a #define plus a _Static_assert in app.c. */
66
+ /* Keyboard ids == SDL scancodes, which are physical *positions* rather than
67
+ * letters: RGAME_KEY_A is the key marked A on a QWERTY board and Q on AZERTY.
68
+ * The set is what a Western keyboard can be relied on to have. No numpad (most
69
+ * laptops have none), no GUI/Windows/Command key, no print-screen cluster, and
70
+ * nothing whose position depends on the layout. Those can be added when
71
+ * something needs them; each is a #define plus a _Static_assert in app.c. */
72
+ #define RGAME_KEY_A 4
73
+ #define RGAME_KEY_B 5
74
+ #define RGAME_KEY_C 6
75
+ #define RGAME_KEY_D 7
76
+ #define RGAME_KEY_E 8
77
+ #define RGAME_KEY_F 9
78
+ #define RGAME_KEY_G 10
79
+ #define RGAME_KEY_H 11
80
+ #define RGAME_KEY_I 12
81
+ #define RGAME_KEY_J 13
82
+ #define RGAME_KEY_K 14
83
+ #define RGAME_KEY_L 15
84
+ #define RGAME_KEY_M 16
85
+ #define RGAME_KEY_N 17
86
+ #define RGAME_KEY_O 18
87
+ #define RGAME_KEY_P 19
88
+ #define RGAME_KEY_Q 20
89
+ #define RGAME_KEY_R 21
90
+ #define RGAME_KEY_S 22
91
+ #define RGAME_KEY_T 23
92
+ #define RGAME_KEY_U 24
93
+ #define RGAME_KEY_V 25
94
+ #define RGAME_KEY_W 26
95
+ #define RGAME_KEY_X 27
96
+ #define RGAME_KEY_Y 28
97
+ #define RGAME_KEY_Z 29
98
+ #define RGAME_KEY_1 30
99
+ #define RGAME_KEY_2 31
100
+ #define RGAME_KEY_3 32
101
+ #define RGAME_KEY_4 33
102
+ #define RGAME_KEY_5 34
103
+ #define RGAME_KEY_6 35
104
+ #define RGAME_KEY_7 36
105
+ #define RGAME_KEY_8 37
106
+ #define RGAME_KEY_9 38
107
+ #define RGAME_KEY_0 39
57
108
  #define RGAME_KEY_RETURN 40
58
109
  #define RGAME_KEY_ESCAPE 41
110
+ #define RGAME_KEY_BACKSPACE 42
111
+ #define RGAME_KEY_TAB 43
59
112
  #define RGAME_KEY_SPACE 44
113
+ #define RGAME_KEY_MINUS 45
114
+ #define RGAME_KEY_EQUALS 46
115
+ #define RGAME_KEY_LEFTBRACKET 47
116
+ #define RGAME_KEY_RIGHTBRACKET 48
117
+ #define RGAME_KEY_BACKSLASH 49
118
+ #define RGAME_KEY_SEMICOLON 51
119
+ #define RGAME_KEY_APOSTROPHE 52
120
+ #define RGAME_KEY_GRAVE 53
121
+ #define RGAME_KEY_COMMA 54
122
+ #define RGAME_KEY_PERIOD 55
123
+ #define RGAME_KEY_SLASH 56
124
+ #define RGAME_KEY_CAPSLOCK 57
60
125
  #define RGAME_KEY_F1 58
126
+ #define RGAME_KEY_F2 59
127
+ #define RGAME_KEY_F3 60
128
+ #define RGAME_KEY_F4 61
129
+ #define RGAME_KEY_F5 62
130
+ #define RGAME_KEY_F6 63
131
+ #define RGAME_KEY_F7 64
132
+ #define RGAME_KEY_F8 65
133
+ #define RGAME_KEY_F9 66
134
+ #define RGAME_KEY_F10 67
135
+ #define RGAME_KEY_F11 68
136
+ #define RGAME_KEY_F12 69
137
+ #define RGAME_KEY_INSERT 73
138
+ #define RGAME_KEY_HOME 74
139
+ #define RGAME_KEY_PAGEUP 75
140
+ #define RGAME_KEY_DELETE 76
141
+ #define RGAME_KEY_END 77
142
+ #define RGAME_KEY_PAGEDOWN 78
61
143
  #define RGAME_KEY_RIGHT 79
62
144
  #define RGAME_KEY_LEFT 80
63
145
  #define RGAME_KEY_DOWN 81
64
146
  #define RGAME_KEY_UP 82
147
+ #define RGAME_KEY_LCTRL 224
148
+ #define RGAME_KEY_LSHIFT 225
149
+ #define RGAME_KEY_LALT 226
150
+ #define RGAME_KEY_RCTRL 228
151
+ #define RGAME_KEY_RSHIFT 229
152
+ #define RGAME_KEY_RALT 230
65
153
 
66
154
  /* Gamepad button ids: the gamepad range plus SDL's controller button number.
67
155
  * app.c asserts each against the SDL constant it mirrors, as with the keys. */
@@ -80,6 +168,12 @@ void rgame_app_destroy(rgame_app *app);
80
168
  #define RGAME_PAD_DPAD_DOWN (RGAME_BUTTON_GAMEPAD_FIRST + 12)
81
169
  #define RGAME_PAD_DPAD_LEFT (RGAME_BUTTON_GAMEPAD_FIRST + 13)
82
170
  #define RGAME_PAD_DPAD_RIGHT (RGAME_BUTTON_GAMEPAD_FIRST + 14)
171
+ #define RGAME_PAD_MISC1 (RGAME_BUTTON_GAMEPAD_FIRST + 15)
172
+ #define RGAME_PAD_PADDLE1 (RGAME_BUTTON_GAMEPAD_FIRST + 16)
173
+ #define RGAME_PAD_PADDLE2 (RGAME_BUTTON_GAMEPAD_FIRST + 17)
174
+ #define RGAME_PAD_PADDLE3 (RGAME_BUTTON_GAMEPAD_FIRST + 18)
175
+ #define RGAME_PAD_PADDLE4 (RGAME_BUTTON_GAMEPAD_FIRST + 19)
176
+ #define RGAME_PAD_TOUCHPAD (RGAME_BUTTON_GAMEPAD_FIRST + 20)
83
177
 
84
178
  /*
85
179
  * Analog axes are their own small id space rather than part of the button
@@ -121,6 +215,7 @@ void rgame_app_destroy(rgame_app *app);
121
215
  typedef void (*rgame_frame_begin_fn)(void *userdata);
122
216
  typedef void (*rgame_update_fn)(void *userdata, double dt_seconds);
123
217
  typedef void (*rgame_draw_fn)(void *userdata);
218
+ typedef void (*rgame_frame_end_fn)(void *userdata);
124
219
  typedef int (*rgame_needs_redraw_fn)(void *userdata);
125
220
  typedef void (*rgame_button_fn)(void *userdata, int button_id);
126
221
  typedef void (*rgame_resize_fn)(void *userdata, int width, int height);
@@ -141,6 +236,12 @@ typedef void (*rgame_gamepad_fn)(void *userdata, int slot);
141
236
  * - `needs_redraw` is polled before drawing; returning 0 skips the draw for
142
237
  * that frame (simulation still advances). NULL means always redraw.
143
238
  * - `draw` renders one frame.
239
+ * - `frame_end` runs once per rendered frame, after that frame's drawing has
240
+ * been submitted to the GPU but *before* the buffer swap — the only point
241
+ * at which the frame just drawn can be read back deterministically. Never
242
+ * called when `needs_redraw` skipped the draw. Rarely needed: it exists for
243
+ * reading pixels back for a test, not for game code, since a real driver is
244
+ * free to swap however it likes once `draw` returns.
144
245
  * - `button_down`/`button_up` report discrete key presses and releases. Key
145
246
  * repeats are filtered out, so holding a key reports exactly one press.
146
247
  * - `resize` reports a new window size; the GL viewport is already updated.
@@ -154,6 +255,7 @@ typedef struct {
154
255
  rgame_update_fn update;
155
256
  rgame_needs_redraw_fn needs_redraw;
156
257
  rgame_draw_fn draw;
258
+ rgame_frame_end_fn frame_end;
157
259
  rgame_button_fn button_down;
158
260
  rgame_button_fn button_up;
159
261
  rgame_resize_fn resize;
@@ -181,6 +283,19 @@ void rgame_app_close(rgame_app *app);
181
283
  int rgame_app_width(const rgame_app *app);
182
284
  int rgame_app_height(const rgame_app *app);
183
285
 
286
+ /*
287
+ * Fullscreen, toggled at any time. This is *desktop* fullscreen: the window
288
+ * takes the whole screen at the screen's own resolution, rather than asking the
289
+ * display to change mode. That makes the switch instant and reversible, costs
290
+ * no mode list to choose from, and leaves other windows where they were.
291
+ *
292
+ * Switching resizes the window, so the `resize` callback fires with the new
293
+ * size — the same path a user dragging a window edge takes. Nothing else needs
294
+ * telling.
295
+ */
296
+ void rgame_app_set_fullscreen(rgame_app *app, int fullscreen);
297
+ int rgame_app_fullscreen(const rgame_app *app);
298
+
184
299
  /* Window title. The returned string is owned by the window, not the caller. */
185
300
  const char *rgame_app_title(const rgame_app *app);
186
301
  void rgame_app_set_title(rgame_app *app, const char *title);
@@ -226,6 +341,18 @@ unsigned int rgame_app_ticks_ms(const rgame_app *app);
226
341
  /* Most recent frames-per-second reading (updated ~once per second). */
227
342
  double rgame_app_fps(const rgame_app *app);
228
343
 
344
+ /*
345
+ * The user's preferred locales, most preferred first, written into `out` as
346
+ * "de-AT,en": a language, a hyphen and a country when the OS names one, and
347
+ * no trailing comma. Needs no app.
348
+ *
349
+ * Returns the length of the whole list, not counting the NUL, like snprintf:
350
+ * a result >= `capacity` means `out` holds a truncated prefix, and a buffer of
351
+ * result + 1 bytes fits it. `out` is always NUL-terminated when `capacity` is
352
+ * non-zero. Returns 0, with `out` empty, when the OS reports none.
353
+ */
354
+ size_t rgame_preferred_locales(char *out, size_t capacity);
355
+
229
356
  /*
230
357
  * ---------------------------------------------------------------------------
231
358
  * Images
@@ -360,7 +487,7 @@ int rgame_app_draw_image_rot(rgame_app *app, const rgame_image *image, float cx,
360
487
  float angle_degrees, float scale, unsigned int color, double z);
361
488
 
362
489
  /*
363
- * The transform and clip stacks. Every push is undone by the same
490
+ * The transform, clip and layer stacks. Every push is undone by the same
364
491
  * `rgame_app_pop`, so a caller can never pop the wrong one; a push that cannot
365
492
  * be honoured (a full stack) is still counted, so pops stay balanced and the
366
493
  * drawing comes out untransformed rather than desynchronised.
@@ -378,6 +505,25 @@ void rgame_app_push_scale(rgame_app *app, float sx, float sy);
378
505
  * always succeed as far as the caller is concerned.
379
506
  */
380
507
  int rgame_app_push_clip(rgame_app *app, int x, int y, int width, int height);
508
+
509
+ /*
510
+ * The layer stack: what every subsequent `z` is measured from, until the
511
+ * matching pop. It *replaces* rather than accumulates, and starts at 0, so a
512
+ * caller that never pushes one gets exactly the z it passes.
513
+ *
514
+ * `rgame_app_next_layer_slot` is the other half: one counter per band, reset at
515
+ * the start of each frame, handing out increasing indices. It counts and
516
+ * nothing more — what a band means, and how a slot index becomes a base, is
517
+ * decided a layer up (RGame::Util::Z). Bands outside 0..7 answer 0.
518
+ *
519
+ * Together they are how a scene graph gives each node its own narrow window of
520
+ * z values: the traversal takes the next slot as it reaches a node, so draw
521
+ * order is tree order, and a node's `z:` can only reorder its own drawing.
522
+ */
523
+ void rgame_app_push_layer(rgame_app *app, double base);
524
+ double rgame_app_layer(rgame_app *app);
525
+ unsigned int rgame_app_next_layer_slot(rgame_app *app, int band);
526
+
381
527
  void rgame_app_pop(rgame_app *app);
382
528
 
383
529
  /*
@@ -11,15 +11,69 @@ _Static_assert(RGAME_GAMEPAD_AXIS_COUNT == SDL_CONTROLLER_AXIS_MAX,
11
11
 
12
12
  /* Gamepad button ids are the gamepad range plus SDL's own button number, so
13
13
  * each named id must line up with the SDL constant it mirrors. */
14
- _Static_assert(RGAME_PAD_A == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_A,
14
+ _Static_assert(RGAME_PAD_A ==
15
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_A,
15
16
  "pad id must match SDL controller button");
16
- _Static_assert(RGAME_PAD_START == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_START,
17
+ _Static_assert(RGAME_PAD_B ==
18
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_B,
17
19
  "pad id must match SDL controller button");
18
- _Static_assert(RGAME_PAD_DPAD_UP == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_UP,
20
+ _Static_assert(RGAME_PAD_X ==
21
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_X,
22
+ "pad id must match SDL controller button");
23
+ _Static_assert(RGAME_PAD_Y ==
24
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_Y,
25
+ "pad id must match SDL controller button");
26
+ _Static_assert(RGAME_PAD_BACK ==
27
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_BACK,
28
+ "pad id must match SDL controller button");
29
+ _Static_assert(RGAME_PAD_GUIDE ==
30
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_GUIDE,
31
+ "pad id must match SDL controller button");
32
+ _Static_assert(RGAME_PAD_START ==
33
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_START,
34
+ "pad id must match SDL controller button");
35
+ _Static_assert(RGAME_PAD_LEFT_STICK ==
36
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_LEFTSTICK,
37
+ "pad id must match SDL controller button");
38
+ _Static_assert(RGAME_PAD_RIGHT_STICK ==
39
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_RIGHTSTICK,
40
+ "pad id must match SDL controller button");
41
+ _Static_assert(RGAME_PAD_LEFT_SHOULDER ==
42
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
43
+ "pad id must match SDL controller button");
44
+ _Static_assert(RGAME_PAD_RIGHT_SHOULDER ==
45
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
46
+ "pad id must match SDL controller button");
47
+ _Static_assert(RGAME_PAD_DPAD_UP ==
48
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_UP,
49
+ "pad id must match SDL controller button");
50
+ _Static_assert(RGAME_PAD_DPAD_DOWN ==
51
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_DOWN,
52
+ "pad id must match SDL controller button");
53
+ _Static_assert(RGAME_PAD_DPAD_LEFT ==
54
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_LEFT,
19
55
  "pad id must match SDL controller button");
20
56
  _Static_assert(RGAME_PAD_DPAD_RIGHT ==
21
57
  RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
22
58
  "pad id must match SDL controller button");
59
+ _Static_assert(RGAME_PAD_MISC1 ==
60
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_MISC1,
61
+ "pad id must match SDL controller button");
62
+ _Static_assert(RGAME_PAD_PADDLE1 ==
63
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE1,
64
+ "pad id must match SDL controller button");
65
+ _Static_assert(RGAME_PAD_PADDLE2 ==
66
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE2,
67
+ "pad id must match SDL controller button");
68
+ _Static_assert(RGAME_PAD_PADDLE3 ==
69
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE3,
70
+ "pad id must match SDL controller button");
71
+ _Static_assert(RGAME_PAD_PADDLE4 ==
72
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE4,
73
+ "pad id must match SDL controller button");
74
+ _Static_assert(RGAME_PAD_TOUCHPAD ==
75
+ RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_TOUCHPAD,
76
+ "pad id must match SDL controller button");
23
77
  _Static_assert(RGAME_AXIS_LEFT_X == SDL_CONTROLLER_AXIS_LEFTX, "axis id must match SDL axis");
24
78
  _Static_assert(RGAME_AXIS_TRIGGER_RIGHT == SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
25
79
  "axis id must match SDL axis");
@@ -114,10 +114,15 @@ static VALUE audio_s_debug_live_sounds(VALUE klass) {
114
114
  * ------------------------------------------------------------------------- */
115
115
 
116
116
  /*
117
- * Both wrap a C handle plus the Ruby Audio it came from. Marking the device is
118
- * what stops the collector taking it first: the sound is a voice inside that
119
- * device's mixer, and freeing the mixer out from under it would be a crash at
120
- * an unpredictable moment.
117
+ * Both wrap a C handle plus the Ruby Audio it came from. Marking the device
118
+ * keeps it alive for as long as any sound made from it is *reachable*: the
119
+ * sound is a voice inside that device's mixer, and freeing the mixer out from
120
+ * under a live sound would be a crash at an unpredictable moment.
121
+ *
122
+ * It is only half the guarantee, and the half it leaves out is worth knowing.
123
+ * When a sound and its device become garbage in the same collection, marking
124
+ * has nothing left to say and the sweep frees them in whatever order it reaches
125
+ * them. audio.c refcounts the device for that case; see audio_release there.
121
126
  */
122
127
  typedef struct {
123
128
  void *handle; /* rgame_sample * or rgame_song * */
@@ -312,7 +317,7 @@ void rgame_init_audio(VALUE mCore) {
312
317
  rb_define_class_under(cSong, "LoadError", rb_eStandardError);
313
318
  rb_define_alloc_func(cSong, song_alloc);
314
319
  rb_define_method(cSong, "initialize", song_initialize, 2);
315
- rb_define_method(cSong, "play_looping", song_play, 1);
320
+ rb_define_private_method(cSong, "play_looping", song_play, 1);
316
321
  rb_define_method(cSong, "stop", song_stop, 0);
317
322
  rb_define_method(cSong, "playing?", song_playing_p, 0);
318
323
  rb_define_method(cSong, "looping?", song_looping_p, 0);
@@ -23,6 +23,9 @@
23
23
  * def button_down(id); end # discrete key press
24
24
  * def button_up(id); end
25
25
  * def frame_begin; end # once per frame, before the tick batch
26
+ * def frame_end; end # after draw is submitted, before the swap —
27
+ * # rarely needed; it exists for reading pixels
28
+ * # back in a test, not for game code
26
29
  * def resize(w, h); end
27
30
  * def gamepad_connected(slot); end
28
31
  * def gamepad_disconnected(slot); end
@@ -47,6 +50,7 @@
47
50
  static ID id_frame_begin;
48
51
  static ID id_update;
49
52
  static ID id_draw;
53
+ static ID id_frame_end;
50
54
  static ID id_needs_redraw;
51
55
  static ID id_button_down;
52
56
  static ID id_button_up;
@@ -57,6 +61,7 @@ static ID id_width;
57
61
  static ID id_height;
58
62
  static ID id_caption;
59
63
  static ID id_media_root;
64
+ static ID id_fullscreen;
60
65
 
61
66
  /* ------------------------------------------------------------------------- *
62
67
  * rgame_app lifetime, wrapped as a Ruby object
@@ -106,8 +111,9 @@ static VALUE app_alloc(VALUE klass) {
106
111
  return TypedData_Wrap_Struct(klass, &app_data_type, NULL);
107
112
  }
108
113
 
109
- /* App.new(width:, height:, caption:) — keyword arguments, matching the shape a
110
- * subclass's own initialize will forward to via super. */
114
+ /* App.new(width:, height:, caption:, media_root:, fullscreen:) — keyword
115
+ * arguments, matching the shape a subclass's own initialize will forward to via
116
+ * super. The first three are required; the last two have defaults. */
111
117
  static VALUE app_initialize(int argc, VALUE *argv, VALUE self) {
112
118
  VALUE opts = Qnil;
113
119
  rb_scan_args(argc, argv, "0:", &opts); /* no positional args, keywords only */
@@ -115,14 +121,19 @@ static VALUE app_initialize(int argc, VALUE *argv, VALUE self) {
115
121
  rb_raise(rb_eArgError, "missing keywords: :width, :height, :caption");
116
122
  }
117
123
 
118
- const ID keys[4] = { id_width, id_height, id_caption, id_media_root };
119
- VALUE values[4];
120
- /* 3 required, 1 optional: raises on a missing or unknown keyword. An absent
124
+ const ID keys[5] = { id_width, id_height, id_caption, id_media_root, id_fullscreen };
125
+ VALUE values[5];
126
+ /* 3 required, 2 optional: raises on a missing or unknown keyword. An absent
121
127
  * optional comes back as Qundef. */
122
- rb_get_kwargs(opts, keys, 3, 1, values);
128
+ rb_get_kwargs(opts, keys, 3, 2, values);
123
129
 
130
+ /* Passed to create rather than set afterwards, so a game that starts
131
+ * fullscreen never shows a windowed frame first. RTEST treats both nil and
132
+ * false as off, so `fullscreen: settings[:fullscreen]` works with a key that
133
+ * is missing. */
124
134
  rgame_app *app = rgame_app_create(NUM2INT(values[0]), NUM2INT(values[1]),
125
- StringValueCStr(values[2]));
135
+ StringValueCStr(values[2]),
136
+ values[4] != Qundef && RTEST(values[4]));
126
137
  if (!app) {
127
138
  rb_raise(rb_eRuntimeError, "failed to create rgame app");
128
139
  }
@@ -265,6 +276,10 @@ static void tramp_draw(void *userdata) {
265
276
  protected_call((run_state *)userdata, id_draw, 0, NULL);
266
277
  }
267
278
 
279
+ static void tramp_frame_end(void *userdata) {
280
+ protected_call((run_state *)userdata, id_frame_end, 0, NULL);
281
+ }
282
+
268
283
  static int tramp_needs_redraw(void *userdata) {
269
284
  VALUE result = protected_call((run_state *)userdata, id_needs_redraw, 0, NULL);
270
285
  return RTEST(result) ? 1 : 0;
@@ -308,6 +323,7 @@ static VALUE app_run(VALUE self) {
308
323
  .update = tramp_update,
309
324
  .needs_redraw = tramp_needs_redraw,
310
325
  .draw = tramp_draw,
326
+ .frame_end = tramp_frame_end,
311
327
  .button_down = tramp_button_down,
312
328
  .button_up = tramp_button_up,
313
329
  .resize = tramp_resize,
@@ -354,6 +370,18 @@ static VALUE app_set_caption(VALUE self, VALUE title) {
354
370
  return title;
355
371
  }
356
372
 
373
+ static VALUE app_fullscreen_p(VALUE self) {
374
+ return rgame_app_fullscreen(rgame_app_unwrap(self)) ? Qtrue : Qfalse;
375
+ }
376
+
377
+ /* Returns what it was given, as an assignment must in Ruby — so
378
+ * `app.fullscreen = settings.fullscreen?` reads back the value that was set,
379
+ * not the window's state. Read #fullscreen? for that. */
380
+ static VALUE app_set_fullscreen(VALUE self, VALUE on) {
381
+ rgame_app_set_fullscreen(rgame_app_unwrap(self), RTEST(on));
382
+ return on;
383
+ }
384
+
357
385
  /*
358
386
  * Raw input queries. These take the numeric device and button/axis ids from
359
387
  * RGame::Core::Input rather than symbolic action names — turning `:fire` into
@@ -421,6 +449,11 @@ static VALUE app_default_draw(VALUE self) {
421
449
  return Qnil;
422
450
  }
423
451
 
452
+ static VALUE app_default_frame_end(VALUE self) {
453
+ (void)self;
454
+ return Qnil;
455
+ }
456
+
424
457
  /* Default is "always redraw", matching a NULL needs_redraw in the C API. */
425
458
  static VALUE app_default_needs_redraw(VALUE self) {
426
459
  (void)self;
@@ -457,6 +490,7 @@ void Init_core_ext(void) {
457
490
  id_frame_begin = rb_intern("frame_begin");
458
491
  id_update = rb_intern("update");
459
492
  id_draw = rb_intern("draw");
493
+ id_frame_end = rb_intern("frame_end");
460
494
  id_needs_redraw = rb_intern("needs_redraw?");
461
495
  id_button_down = rb_intern("button_down");
462
496
  id_button_up = rb_intern("button_up");
@@ -467,6 +501,7 @@ void Init_core_ext(void) {
467
501
  id_height = rb_intern("height");
468
502
  id_caption = rb_intern("caption");
469
503
  id_media_root = rb_intern("media_root");
504
+ id_fullscreen = rb_intern("fullscreen");
470
505
 
471
506
  /*
472
507
  * rb_define_module is idempotent — it returns the existing RGame if some
@@ -486,6 +521,8 @@ void Init_core_ext(void) {
486
521
  rb_define_method(cApp, "height", app_height, 0);
487
522
  rb_define_method(cApp, "caption", app_caption, 0);
488
523
  rb_define_method(cApp, "caption=", app_set_caption, 1);
524
+ rb_define_method(cApp, "fullscreen?", app_fullscreen_p, 0);
525
+ rb_define_method(cApp, "fullscreen=", app_set_fullscreen, 1);
489
526
  rb_define_method(cApp, "ticks_ms", app_ticks_ms, 0);
490
527
  rb_define_method(cApp, "fps", app_fps, 0);
491
528
  rb_define_method(cApp, "input_down?", app_input_down_p, 2);
@@ -498,6 +535,7 @@ void Init_core_ext(void) {
498
535
  rb_define_method(cApp, "frame_begin", app_default_frame_begin, 0);
499
536
  rb_define_method(cApp, "update", app_default_update, 1);
500
537
  rb_define_method(cApp, "draw", app_default_draw, 0);
538
+ rb_define_method(cApp, "frame_end", app_default_frame_end, 0);
501
539
  rb_define_method(cApp, "needs_redraw?", app_default_needs_redraw, 0);
502
540
  rb_define_method(cApp, "button_down", app_default_button, 1);
503
541
  rb_define_method(cApp, "button_up", app_default_button, 1);
@@ -510,4 +548,5 @@ void Init_core_ext(void) {
510
548
  rgame_init_recording(mCore);
511
549
  rgame_init_font(mCore);
512
550
  rgame_init_audio(mCore);
551
+ rgame_init_locale(mCore);
513
552
  }
@@ -21,6 +21,9 @@ void rgame_init_renderer(VALUE mCore);
21
21
  void rgame_init_recording(VALUE mCore);
22
22
  void rgame_init_font(VALUE mCore);
23
23
 
24
+ /* Not a class: RGame::Core.preferred_locales, a module function. */
25
+ void rgame_init_locale(VALUE mCore);
26
+
24
27
  /* Defines Audio, Sample and Song together — see audio_ext.c for why those three
25
28
  * share a file when everything else here does not. */
26
29
  void rgame_init_audio(VALUE mCore);
@@ -0,0 +1,44 @@
1
+ /*
2
+ * locale_ext.c — the Ruby binding for RGame::Core.preferred_locales.
3
+ *
4
+ * RGame::Core.preferred_locales # => ["de-AT", "en"]
5
+ *
6
+ * A module function rather than an App method because SDL answers without an
7
+ * app, or even SDL_Init. The reading is locale.c; this file only sizes a buffer
8
+ * and splits the list. Choosing a language from the answer is
9
+ * RGame::Engine::I18n.choose, which the glue calls.
10
+ */
11
+
12
+ #include "ruby/core_ext.h"
13
+
14
+ #include "rgame/core.h"
15
+
16
+ /*
17
+ * The user's preferred locales as Strings, most preferred first, or [] when the
18
+ * OS names none. Never contains nil or an empty String.
19
+ */
20
+ static VALUE core_preferred_locales(VALUE self) {
21
+ (void)self;
22
+
23
+ char stack[256];
24
+ size_t length = rgame_preferred_locales(stack, sizeof stack);
25
+ if (length < sizeof stack) {
26
+ return rb_str_split(rb_utf8_str_new(stack, (long)length), ",");
27
+ }
28
+
29
+ /* Too long for the stack. The environment is read on every call, so the
30
+ * list can grow between two of them: size again until what was written fits. */
31
+ for (;;) {
32
+ VALUE list = rb_utf8_str_new(NULL, (long)length);
33
+ size_t written = rgame_preferred_locales(RSTRING_PTR(list), length + 1);
34
+ if (written <= length) {
35
+ rb_str_set_len(list, (long)written);
36
+ return rb_str_split(list, ",");
37
+ }
38
+ length = written;
39
+ }
40
+ }
41
+
42
+ void rgame_init_locale(VALUE mCore) {
43
+ rb_define_module_function(mCore, "preferred_locales", core_preferred_locales, 0);
44
+ }
@@ -176,7 +176,7 @@ void rgame_init_recording(VALUE mCore) {
176
176
 
177
177
  rb_define_alloc_func(cRecording, recording_alloc);
178
178
  rb_define_singleton_method(cRecording, "new", recording_s_new, -1);
179
- rb_define_method(cRecording, "draw_at", recording_draw_at, 4);
179
+ rb_define_private_method(cRecording, "draw_at", recording_draw_at, 4);
180
180
  rb_define_method(cRecording, "batch_count", recording_batch_count, 0);
181
181
  rb_define_method(cRecording, "vertex_count", recording_vertex_count, 0);
182
182
  rb_define_method(cRecording, "empty?", recording_empty_p, 0);
@@ -297,6 +297,26 @@ static VALUE renderer_push_clip(VALUE self, VALUE x, VALUE y, VALUE width, VALUE
297
297
  return self;
298
298
  }
299
299
 
300
+ /*
301
+ * #push_layer(base) / #layer — the base every subsequent z is measured from.
302
+ *
303
+ * `base` is a whole number well inside a double's exact range (see
304
+ * RGame::Util::Z), so it crosses as a double and comes back as a Float.
305
+ */
306
+ static VALUE renderer_push_layer(VALUE self, VALUE base) {
307
+ rgame_app_push_layer(drawing_app(self), NUM2DBL(base));
308
+ return self;
309
+ }
310
+
311
+ static VALUE renderer_layer(VALUE self) {
312
+ return DBL2NUM(rgame_app_layer(drawing_app(self)));
313
+ }
314
+
315
+ /* #next_layer_slot(band) -> the next slot index in that band this frame. */
316
+ static VALUE renderer_next_layer_slot(VALUE self, VALUE band) {
317
+ return UINT2NUM(rgame_app_next_layer_slot(drawing_app(self), NUM2INT(band)));
318
+ }
319
+
300
320
  /* ------------------------------------------------------------------------- *
301
321
  * Recording
302
322
  *
@@ -354,23 +374,26 @@ void rgame_init_renderer(VALUE mCore) {
354
374
  rb_define_method(cRenderer, "drawing?", renderer_drawing_p, 0);
355
375
  rb_define_method(cRenderer, "app", renderer_app, 0);
356
376
 
357
- rb_define_method(cRenderer, "draw_rect", renderer_draw_rect, 6);
358
- rb_define_method(cRenderer, "draw_quad", renderer_draw_quad, -1);
359
- rb_define_method(cRenderer, "draw_triangle", renderer_draw_triangle, -1);
360
- rb_define_method(cRenderer, "draw_line", renderer_draw_line, -1);
361
- rb_define_method(cRenderer, "draw_circle", renderer_draw_circle, -1);
362
- rb_define_method(cRenderer, "draw_image", renderer_draw_image, 5);
363
- rb_define_method(cRenderer, "draw_image_scaled", renderer_draw_image_scaled, -1);
364
- rb_define_method(cRenderer, "draw_image_rot", renderer_draw_image_rot, -1);
365
- rb_define_method(cRenderer, "draw_text", renderer_draw_text, -1);
366
-
367
- rb_define_method(cRenderer, "push_translate", renderer_push_translate, 2);
368
- rb_define_method(cRenderer, "push_rotate", renderer_push_rotate, 3);
369
- rb_define_method(cRenderer, "push_scale", renderer_push_scale, 2);
370
- rb_define_method(cRenderer, "push_clip", renderer_push_clip, 4);
371
- rb_define_method(cRenderer, "pop", renderer_pop, 0);
372
-
373
- rb_define_method(cRenderer, "begin_record", renderer_begin_record, 0);
374
- rb_define_method(cRenderer, "end_record", renderer_end_record, 0);
375
- rb_define_method(cRenderer, "cancel_record", renderer_cancel_record, 0);
377
+ rb_define_private_method(cRenderer, "draw_rect", renderer_draw_rect, 6);
378
+ rb_define_private_method(cRenderer, "draw_quad", renderer_draw_quad, -1);
379
+ rb_define_private_method(cRenderer, "draw_triangle", renderer_draw_triangle, -1);
380
+ rb_define_private_method(cRenderer, "draw_line", renderer_draw_line, -1);
381
+ rb_define_private_method(cRenderer, "draw_circle", renderer_draw_circle, -1);
382
+ rb_define_private_method(cRenderer, "draw_image", renderer_draw_image, 5);
383
+ rb_define_private_method(cRenderer, "draw_image_scaled", renderer_draw_image_scaled, -1);
384
+ rb_define_private_method(cRenderer, "draw_image_rot", renderer_draw_image_rot, -1);
385
+ rb_define_private_method(cRenderer, "draw_text", renderer_draw_text, -1);
386
+
387
+ rb_define_private_method(cRenderer, "push_translate", renderer_push_translate, 2);
388
+ rb_define_private_method(cRenderer, "push_rotate", renderer_push_rotate, 3);
389
+ rb_define_private_method(cRenderer, "push_scale", renderer_push_scale, 2);
390
+ rb_define_private_method(cRenderer, "push_clip", renderer_push_clip, 4);
391
+ rb_define_private_method(cRenderer, "push_layer", renderer_push_layer, 1);
392
+ rb_define_method(cRenderer, "layer", renderer_layer, 0);
393
+ rb_define_private_method(cRenderer, "next_layer_slot", renderer_next_layer_slot, 1);
394
+ rb_define_private_method(cRenderer, "pop", renderer_pop, 0);
395
+
396
+ rb_define_private_method(cRenderer, "begin_record", renderer_begin_record, 0);
397
+ rb_define_private_method(cRenderer, "end_record", renderer_end_record, 0);
398
+ rb_define_private_method(cRenderer, "cancel_record", renderer_cancel_record, 0);
376
399
  }
@@ -63,10 +63,19 @@ static VALUE color_s_new(int argc, VALUE *argv, VALUE klass) {
63
63
  NIL_P(a) ? 255 : component(a, "alpha")));
64
64
  }
65
65
 
66
- /* Color.from_packed(0xRRGGBBAA) */
66
+ /* Color.from_packed(0xRRGGBBAA)
67
+ *
68
+ * The bounds check below needs a C type wider than 32 bits so a
69
+ * one-bit-too-wide value can be caught rather than silently truncated.
70
+ * `unsigned long` is that on Linux/macOS (LP64: 64 bits) but not on Windows
71
+ * (LLP64: `long` stays 32 bits even in a 64-bit build), where NUM2ULONG would
72
+ * itself raise RangeError on the very value this function means to turn into
73
+ * a clean ArgumentError. NUM2ULL is unsigned long long, 64 bits on every
74
+ * platform this project builds for, so the check below runs before either
75
+ * platform's integer width can get in the way. */
67
76
  static VALUE color_s_from_packed(VALUE klass, VALUE packed) {
68
- unsigned long value = NUM2ULONG(packed);
69
- if (value > 0xFFFFFFFFul) {
77
+ unsigned long long value = NUM2ULL(packed);
78
+ if (value > 0xFFFFFFFFull) {
70
79
  rb_raise(rb_eArgError, "packed colour must fit in 32 bits");
71
80
  }
72
81
  return color_wrap(klass, (rgame_color)value);