mima-engine 0.4.0__py3-none-any.whl

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 (153) hide show
  1. mima/__init__.py +4 -0
  2. mima/backend/__init__.py +1 -0
  3. mima/backend/pygame_assets.py +401 -0
  4. mima/backend/pygame_audio.py +78 -0
  5. mima/backend/pygame_backend.py +603 -0
  6. mima/backend/pygame_camera.py +63 -0
  7. mima/backend/pygame_events.py +695 -0
  8. mima/backend/touch_control_scheme_a.py +126 -0
  9. mima/backend/touch_control_scheme_b.py +132 -0
  10. mima/core/__init__.py +0 -0
  11. mima/core/collision.py +325 -0
  12. mima/core/database.py +58 -0
  13. mima/core/engine.py +367 -0
  14. mima/core/mode_engine.py +81 -0
  15. mima/core/scene_engine.py +81 -0
  16. mima/integrated/__init__.py +0 -0
  17. mima/integrated/entity.py +183 -0
  18. mima/integrated/layered_map.py +351 -0
  19. mima/integrated/sprite.py +156 -0
  20. mima/layered/__init__.py +0 -0
  21. mima/layered/assets.py +56 -0
  22. mima/layered/scene.py +415 -0
  23. mima/layered/shape.py +99 -0
  24. mima/layered/shaped_sprite.py +78 -0
  25. mima/layered/virtual_input.py +302 -0
  26. mima/maps/__init__.py +0 -0
  27. mima/maps/template.py +71 -0
  28. mima/maps/tile.py +20 -0
  29. mima/maps/tile_animation.py +7 -0
  30. mima/maps/tile_info.py +10 -0
  31. mima/maps/tile_layer.py +52 -0
  32. mima/maps/tiled/__init__.py +0 -0
  33. mima/maps/tiled/tiled_layer.py +48 -0
  34. mima/maps/tiled/tiled_map.py +95 -0
  35. mima/maps/tiled/tiled_object.py +79 -0
  36. mima/maps/tiled/tiled_objectgroup.py +25 -0
  37. mima/maps/tiled/tiled_template.py +49 -0
  38. mima/maps/tiled/tiled_tile.py +90 -0
  39. mima/maps/tiled/tiled_tileset.py +51 -0
  40. mima/maps/tilemap.py +216 -0
  41. mima/maps/tileset.py +39 -0
  42. mima/maps/tileset_info.py +9 -0
  43. mima/maps/transition_map.py +146 -0
  44. mima/objects/__init__.py +0 -0
  45. mima/objects/animated_sprite.py +217 -0
  46. mima/objects/attribute_effect.py +26 -0
  47. mima/objects/attributes.py +126 -0
  48. mima/objects/creature.py +384 -0
  49. mima/objects/dynamic.py +206 -0
  50. mima/objects/effects/__init__.py +0 -0
  51. mima/objects/effects/colorize_screen.py +60 -0
  52. mima/objects/effects/debug_box.py +133 -0
  53. mima/objects/effects/light.py +103 -0
  54. mima/objects/effects/show_sprite.py +50 -0
  55. mima/objects/effects/walking_on_grass.py +70 -0
  56. mima/objects/effects/walking_on_water.py +57 -0
  57. mima/objects/loader.py +111 -0
  58. mima/objects/projectile.py +111 -0
  59. mima/objects/sprite.py +116 -0
  60. mima/objects/world/__init__.py +0 -0
  61. mima/objects/world/color_gate.py +67 -0
  62. mima/objects/world/color_switch.py +101 -0
  63. mima/objects/world/container.py +175 -0
  64. mima/objects/world/floor_switch.py +109 -0
  65. mima/objects/world/gate.py +178 -0
  66. mima/objects/world/light_source.py +121 -0
  67. mima/objects/world/logic_gate.py +157 -0
  68. mima/objects/world/movable.py +399 -0
  69. mima/objects/world/oneway.py +195 -0
  70. mima/objects/world/pickup.py +157 -0
  71. mima/objects/world/switch.py +179 -0
  72. mima/objects/world/teleport.py +308 -0
  73. mima/py.typed +0 -0
  74. mima/scripts/__init__.py +2 -0
  75. mima/scripts/command.py +38 -0
  76. mima/scripts/commands/__init__.py +0 -0
  77. mima/scripts/commands/add_quest.py +19 -0
  78. mima/scripts/commands/change_map.py +34 -0
  79. mima/scripts/commands/close_dialog.py +9 -0
  80. mima/scripts/commands/equip_weapon.py +23 -0
  81. mima/scripts/commands/give_item.py +26 -0
  82. mima/scripts/commands/give_resource.py +51 -0
  83. mima/scripts/commands/move_map.py +152 -0
  84. mima/scripts/commands/move_to.py +49 -0
  85. mima/scripts/commands/oneway_move.py +58 -0
  86. mima/scripts/commands/parallel.py +66 -0
  87. mima/scripts/commands/play_sound.py +13 -0
  88. mima/scripts/commands/present_item.py +53 -0
  89. mima/scripts/commands/progress_quest.py +12 -0
  90. mima/scripts/commands/quit_game.py +8 -0
  91. mima/scripts/commands/save_game.py +14 -0
  92. mima/scripts/commands/screen_fade.py +83 -0
  93. mima/scripts/commands/serial.py +69 -0
  94. mima/scripts/commands/set_facing_direction.py +21 -0
  95. mima/scripts/commands/set_spawn_map.py +17 -0
  96. mima/scripts/commands/show_choices.py +52 -0
  97. mima/scripts/commands/show_dialog.py +118 -0
  98. mima/scripts/commands/take_coins.py +23 -0
  99. mima/scripts/script_processor.py +61 -0
  100. mima/standalone/__init__.py +0 -0
  101. mima/standalone/camera.py +153 -0
  102. mima/standalone/geometry.py +1318 -0
  103. mima/standalone/multicolumn_list.py +54 -0
  104. mima/standalone/pixel_font.py +84 -0
  105. mima/standalone/scripting.py +145 -0
  106. mima/standalone/spatial.py +186 -0
  107. mima/standalone/sprite.py +158 -0
  108. mima/standalone/tiled_map.py +1247 -0
  109. mima/standalone/transformed_view.py +433 -0
  110. mima/standalone/user_input.py +563 -0
  111. mima/states/__init__.py +0 -0
  112. mima/states/game_state.py +189 -0
  113. mima/states/memory.py +28 -0
  114. mima/states/quest.py +71 -0
  115. mima/types/__init__.py +0 -0
  116. mima/types/alignment.py +7 -0
  117. mima/types/blend.py +8 -0
  118. mima/types/damage.py +42 -0
  119. mima/types/direction.py +44 -0
  120. mima/types/gate_color.py +7 -0
  121. mima/types/graphic_state.py +23 -0
  122. mima/types/keys.py +64 -0
  123. mima/types/mode.py +9 -0
  124. mima/types/nature.py +12 -0
  125. mima/types/object.py +22 -0
  126. mima/types/player.py +9 -0
  127. mima/types/position.py +13 -0
  128. mima/types/start.py +7 -0
  129. mima/types/terrain.py +9 -0
  130. mima/types/tile_collision.py +11 -0
  131. mima/types/weapon_slot.py +6 -0
  132. mima/types/window.py +44 -0
  133. mima/usables/__init__.py +0 -0
  134. mima/usables/item.py +51 -0
  135. mima/usables/weapon.py +68 -0
  136. mima/util/__init__.py +1 -0
  137. mima/util/colors.py +50 -0
  138. mima/util/constants.py +55 -0
  139. mima/util/functions.py +38 -0
  140. mima/util/input_defaults.py +170 -0
  141. mima/util/logging.py +51 -0
  142. mima/util/property.py +8 -0
  143. mima/util/runtime_config.py +327 -0
  144. mima/util/trading_item.py +23 -0
  145. mima/view/__init__.py +0 -0
  146. mima/view/camera.py +192 -0
  147. mima/view/mima_mode.py +618 -0
  148. mima/view/mima_scene.py +231 -0
  149. mima/view/mima_view.py +12 -0
  150. mima/view/mima_window.py +244 -0
  151. mima_engine-0.4.0.dist-info/METADATA +47 -0
  152. mima_engine-0.4.0.dist-info/RECORD +153 -0
  153. mima_engine-0.4.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,433 @@
1
+ import logging
2
+ import math
3
+
4
+ import pygame
5
+ from pygame import Surface, Vector2
6
+ from pygame.event import Event
7
+ from typing_extensions import TypeAlias, Union
8
+
9
+ LOG = logging.getLogger(__name__)
10
+ Color: TypeAlias = Union[tuple[int, int, int], tuple[int, int, int, int]]
11
+ Cache: TypeAlias = dict[
12
+ tuple[int, tuple[int, int, int, int], float, float], tuple[Surface, int]
13
+ ]
14
+ Point: TypeAlias = Union[Vector2, tuple[float, float]]
15
+ Area: TypeAlias = tuple[Point, Point]
16
+
17
+
18
+ class TransformedView:
19
+ """TransformedView inspired by olc PixelGameEngine."""
20
+
21
+ def __init__(
22
+ self,
23
+ screen: Surface,
24
+ view_area: Vector2,
25
+ pixel_size: Vector2 | None = None,
26
+ world_scale: float = 1.0,
27
+ ) -> None:
28
+ self._screen: Surface = screen
29
+
30
+ self._pos: Vector2 = Vector2(0, 0)
31
+ self._view_area: Vector2 = Vector2(view_area)
32
+ self._world_offset: Vector2 = Vector2(0.0, 0.0)
33
+ self._pixel_scale: Vector2 = (
34
+ Vector2(1.0, 1.0) if pixel_size is None else pixel_size
35
+ )
36
+ self._world_scale: Vector2 = Vector2(world_scale, world_scale)
37
+
38
+ self._recip_pixel_scale: Vector2 = Vector2(
39
+ 1.0 / self._pixel_scale.x, 1.0 / self._pixel_scale.y
40
+ )
41
+ self._start_pan: Vector2 = Vector2(0.0, 0.0)
42
+ self._scale_max: Vector2 = Vector2(0.0, 0.0)
43
+ self._scale_min: Vector2 = Vector2(0.0, 0.0)
44
+
45
+ self._steps = 0.1
46
+ self._mouse_button = 2
47
+ self._is_panning: bool = False
48
+ self._clamp_zoom: bool = False
49
+ self._cache: dict = {}
50
+ self._cache_surfaces: bool = False
51
+ self._memory_usage: int = 0
52
+
53
+ def enable_caching(self, enable: bool) -> None:
54
+ self._cache_surfaces = enable
55
+
56
+ def set_pos(self, pos: Vector2) -> None:
57
+ self._pos = pos
58
+
59
+ def get_pos(self) -> Vector2:
60
+ return self._pos
61
+
62
+ def set_view_area(self, area: Vector2) -> None:
63
+ self._view_area = area
64
+
65
+ def get_view_area(self) -> Vector2:
66
+ return self._view_area
67
+
68
+ def set_world_offset(self, offset: Vector2) -> None:
69
+ self._world_offset = offset
70
+
71
+ def move_world_offset(self, delta_offset: Vector2) -> None:
72
+ self._world_offset += delta_offset
73
+
74
+ def set_world_scale(self, scale: Vector2) -> None:
75
+ if scale.x == 0 or scale.y == 0:
76
+ return
77
+ self._world_scale = scale
78
+ if self._clamp_zoom:
79
+ self._world_scale = clamp(
80
+ self._world_scale, self._scale_min, self._scale_max
81
+ )
82
+
83
+ def get_world_tl(self) -> Vector2:
84
+ return self.screen_to_world(Vector2(0, 0))
85
+
86
+ def get_world_br(self) -> Vector2:
87
+ return self.screen_to_world(self._view_area)
88
+
89
+ def get_world_visible_area(self) -> Vector2:
90
+ return self.get_world_br() - self.get_world_tl()
91
+
92
+ def set_scale_extents(self, scale_min: Vector2, scale_max: Vector2) -> None:
93
+ self._scale_max = scale_max
94
+ self._scale_min = scale_min
95
+
96
+ def enable_scale_clamp(self, enable: bool) -> None:
97
+ self._clamp_zoom = enable
98
+
99
+ def zoom_at_screen_pos(self, delta_zoom: float, pos: Vector2) -> None:
100
+ offset_before_zoom = self.screen_to_world(pos)
101
+ self._world_scale *= delta_zoom
102
+ if self._clamp_zoom:
103
+ self._world_scale = clamp(
104
+ self._world_scale, self._scale_min, self._scale_max
105
+ )
106
+ offset_after_zoom = self.screen_to_world(pos)
107
+ # msg = (
108
+ # f"Pos: {pos} OBZ: {offset_before_zoom} OAZ: {offset_after_zoom} "
109
+ # f"OFF: {self._world_offset} -- "
110
+ # )
111
+ self._world_offset += offset_before_zoom - offset_after_zoom
112
+ # msg += f"{self._world_offset}"
113
+ # print(msg)
114
+
115
+ def start_pan(self, pos: Vector2):
116
+ self._is_panning = True
117
+ self._start_pan = Vector2(pos)
118
+
119
+ def update_pan(self, pos: Vector2):
120
+ if self._is_panning:
121
+ delta = pos - self._start_pan
122
+ delta.x /= self._world_scale.x
123
+ delta.y /= self._world_scale.y
124
+ self._world_offset -= delta
125
+ self._start_pan = pos
126
+
127
+ def end_pan(self, pos: Vector2):
128
+ self.update_pan(pos)
129
+ self._is_panning = False
130
+
131
+ def get_world_offset(self) -> Vector2:
132
+ return self._world_offset
133
+
134
+ def get_world_scale(self) -> Vector2:
135
+ return self._world_scale
136
+
137
+ def world_to_screen(self, world_pos: Vector2) -> Vector2:
138
+ return (world_pos - self._world_offset).elementwise() * self._world_scale
139
+
140
+ def screen_to_world(self, screen_pos: Vector2) -> Vector2:
141
+ return self._world_offset + screen_pos.elementwise() / self._world_scale
142
+
143
+ def scale_to_world(self, screen_size: Vector2) -> Vector2:
144
+ return Vector2(
145
+ screen_size.x / self._world_scale.x, screen_size.y / self._world_scale.y
146
+ )
147
+
148
+ def handle_pan_and_zoom(
149
+ self,
150
+ mouse_button: int = 2,
151
+ zoom_rate: float = 0.1,
152
+ pan: bool = True,
153
+ zoom: bool = True,
154
+ events: list[Event] | None = None,
155
+ ) -> list[Event]:
156
+ if events is None:
157
+ events = []
158
+ if not events:
159
+ for event in pygame.event.get():
160
+ events.append(event)
161
+
162
+ for event in events:
163
+ if zoom and event.type == pygame.MOUSEWHEEL:
164
+ if event.y == 1:
165
+ self.zoom_at_screen_pos(
166
+ 1.0 + zoom_rate, Vector2(pygame.mouse.get_pos())
167
+ )
168
+ if event.y == -1:
169
+ self.zoom_at_screen_pos(
170
+ 1.0 - zoom_rate, Vector2(pygame.mouse.get_pos())
171
+ )
172
+ if pan and event.type == pygame.MOUSEBUTTONDOWN:
173
+ if event.button == mouse_button:
174
+ self.start_pan(event.pos)
175
+ if pan and event.type == pygame.MOUSEBUTTONUP:
176
+ if event.button == mouse_button:
177
+ self.end_pan(event.pos)
178
+
179
+ self.update_pan(Vector2(pygame.mouse.get_pos()))
180
+
181
+ return events
182
+
183
+ def is_rect_visible(self, pos: Vector2, size: Vector2) -> bool:
184
+ screen_pos = self.world_to_screen(pos)
185
+ screen_size = (size.x * self._world_scale.x, size.y * self._world_scale.y)
186
+
187
+ return (
188
+ screen_pos.x < self._view_area.x
189
+ and screen_pos.x + screen_size[0] > 0
190
+ and screen_pos.y < self._view_area.y
191
+ and screen_pos.y + screen_size[1] > 0
192
+ )
193
+
194
+ def clear(self, color: Color) -> None:
195
+ pygame.draw.rect(self._screen, color, (self._world_offset, self._view_area))
196
+
197
+ def draw(
198
+ self, pos: Vector2, color: tuple[int, int, int] | tuple[int, int, int, int]
199
+ ) -> None:
200
+ pos = self.world_to_screen(pos)
201
+ self._screen.set_at((int(pos.x), int(pos.y)), color)
202
+
203
+ def draw_line(
204
+ self, pos1: Vector2, pos2: Vector2, color: Color | None = None, width: int = 1
205
+ ) -> None:
206
+ if color is None:
207
+ color = (255, 255, 255)
208
+
209
+ pygame.draw.line(
210
+ surface=self._screen,
211
+ color=color,
212
+ start_pos=(self.world_to_screen(pos1) + self._pos).elementwise()
213
+ * self._pixel_scale,
214
+ end_pos=(self.world_to_screen(pos2) + self._pos).elementwise()
215
+ * self._pixel_scale,
216
+ width=width,
217
+ )
218
+
219
+ def draw_circle(
220
+ self,
221
+ pos: Vector2,
222
+ radius: float,
223
+ color: Color | None = None,
224
+ *,
225
+ width: int = 1,
226
+ draw_top_left: bool = False,
227
+ draw_top_right: bool = False,
228
+ draw_bottom_left: bool = False,
229
+ draw_bottom_right: bool = False,
230
+ ) -> None:
231
+ if color is None:
232
+ color = (255, 255, 255)
233
+
234
+ pygame.draw.circle(
235
+ surface=self._screen,
236
+ color=color,
237
+ center=(self.world_to_screen(pos) + self._pos).elementwise()
238
+ * self._pixel_scale,
239
+ radius=radius * self._world_scale.x * self._pixel_scale.x,
240
+ width=width,
241
+ draw_top_left=draw_top_left,
242
+ draw_top_right=draw_top_right,
243
+ draw_bottom_left=draw_bottom_left,
244
+ draw_bottom_right=draw_bottom_right,
245
+ )
246
+
247
+ def fill_circle(
248
+ self,
249
+ pos: Vector2,
250
+ radius: float,
251
+ color: Color | None = None,
252
+ *,
253
+ draw_top_left: bool = False,
254
+ draw_top_right: bool = False,
255
+ draw_bottom_left: bool = False,
256
+ draw_bottom_right: bool = False,
257
+ ) -> None:
258
+ self.draw_circle(
259
+ pos=pos,
260
+ radius=radius,
261
+ color=color,
262
+ width=0,
263
+ draw_top_left=draw_top_left,
264
+ draw_top_right=draw_top_right,
265
+ draw_bottom_left=draw_bottom_left,
266
+ draw_bottom_right=draw_bottom_right,
267
+ )
268
+
269
+ def draw_rect(
270
+ self,
271
+ pos: Vector2,
272
+ size: Vector2,
273
+ color: Color | None = None,
274
+ *,
275
+ width: int = 1,
276
+ border_top_left_radius: int = -1,
277
+ border_top_right_radius: int = -1,
278
+ border_bottom_left_radius: int = -1,
279
+ border_bottom_right_radius: int = -1,
280
+ ) -> None:
281
+ if color is None:
282
+ color = (255, 255, 255)
283
+ pygame.draw.rect(
284
+ surface=self._screen,
285
+ color=color,
286
+ rect=(
287
+ (self.world_to_screen(pos) + self._pos).elementwise()
288
+ * self._pixel_scale.elementwise(),
289
+ (
290
+ size.elementwise()
291
+ * self._world_scale.elementwise()
292
+ * self._pixel_scale.elementwise()
293
+ ),
294
+ ),
295
+ width=width,
296
+ border_top_left_radius=border_top_left_radius,
297
+ border_top_right_radius=border_top_right_radius,
298
+ border_bottom_left_radius=border_bottom_left_radius,
299
+ border_bottom_right_radius=border_bottom_right_radius,
300
+ )
301
+
302
+ def fill_rect(
303
+ self,
304
+ pos: Vector2,
305
+ size: Vector2,
306
+ color: Color | None = None,
307
+ *,
308
+ border_top_left_radius: int = -1,
309
+ border_top_right_radius: int = -1,
310
+ border_bottom_left_radius: int = -1,
311
+ border_bottom_right_radius: int = -1,
312
+ ) -> None:
313
+ self.draw_rect(
314
+ pos=pos,
315
+ size=size,
316
+ color=color,
317
+ width=0,
318
+ border_top_left_radius=border_top_left_radius,
319
+ border_top_right_radius=border_top_right_radius,
320
+ border_bottom_left_radius=border_bottom_left_radius,
321
+ border_bottom_right_radius=border_bottom_right_radius,
322
+ )
323
+
324
+ def draw_surface(
325
+ self,
326
+ pos: Vector2,
327
+ surf: Surface,
328
+ *,
329
+ src_pos: Vector2 | None = None,
330
+ src_size: Vector2 | None = None,
331
+ scale: float = 1.0,
332
+ angle: float = 0,
333
+ cache: bool = False,
334
+ special_flags: int = 0,
335
+ ) -> None:
336
+ src_size = src_size if src_size is not None else Vector2(surf.get_size())
337
+
338
+ if not self.is_rect_visible(pos, src_size * scale):
339
+ return
340
+
341
+ scaling_factor = scale * (
342
+ self._world_scale.elementwise()
343
+ # * self._recip_pixel_scale.elementwise()
344
+ # * src_size.elementwise()
345
+ )
346
+ dest = self.world_to_screen(pos)
347
+
348
+ area = None
349
+ if src_pos is not None:
350
+ area = (int(src_pos.x), int(src_pos.y), int(src_size.x), int(src_size.y))
351
+
352
+ abs_pos = vfloor((dest + self._pos).elementwise() * self._pixel_scale)
353
+ if scaling_factor.x == 1.0 and scaling_factor.y == 0.0 and angle == 0.0:
354
+ self._screen.blit(surf, abs_pos, area, special_flags)
355
+ return
356
+
357
+ src = surf.subsurface(area) if area is not None else surf
358
+ cache_key = (surf, area, scaling_factor.x, scaling_factor.y, angle)
359
+ if cache_key in self._cache:
360
+ src = self._cache[cache_key]
361
+ else:
362
+ if angle != 0.0:
363
+ src = pygame.transform.rotate(src, angle)
364
+ if scaling_factor.x != 0.0 or scaling_factor.y != 0.0:
365
+ src = pygame.transform.scale(
366
+ src,
367
+ (
368
+ int(src.get_width() * scaling_factor.x),
369
+ int(src.get_height() * scaling_factor.y),
370
+ ),
371
+ )
372
+ if self._cache_surfaces or cache:
373
+ self._cache[cache_key] = src
374
+ self._memory_usage += src.get_width() * src.get_height() * 4
375
+ LOG.debug(
376
+ "Loaded %s to cache. Cache memory usage: %.2f MB ",
377
+ cache_key,
378
+ (self._memory_usage / 2**20),
379
+ )
380
+
381
+ # transformed = pygame.transform.rotozoom(src, angle, scaling_factor.x)
382
+ self._screen.blit(src, abs_pos, special_flags=special_flags)
383
+
384
+
385
+ class TileTransformedView(TransformedView):
386
+ def get_tl_tile(self) -> Vector2:
387
+ res = vfloor(self.screen_to_world(Vector2(0, 0)), inplace=True)
388
+ return res
389
+
390
+ def get_br_tile(self) -> Vector2:
391
+ res = vceil(
392
+ self.screen_to_world(self._view_area.elementwise() / self._pixel_scale),
393
+ inplace=True,
394
+ )
395
+ return res
396
+
397
+ def get_visible_tiles(self) -> Vector2:
398
+ return self.get_br_tile() - self.get_tl_tile()
399
+
400
+ def get_tile_under_screen_pos(self, pos: Vector2) -> Vector2:
401
+ return vfloor(self.screen_to_world(pos))
402
+
403
+ def get_tile_offset(self) -> Vector2:
404
+ return Vector2(
405
+ int(
406
+ (self._world_offset.x - math.floor(self._world_offset.x))
407
+ * self._world_scale.x
408
+ ),
409
+ int(
410
+ (self._world_offset.y - math.floor(self._world_offset.y))
411
+ * self._world_scale.y
412
+ ),
413
+ )
414
+
415
+
416
+ def clamp(val: Vector2, low: Vector2, high: Vector2) -> Vector2:
417
+ return Vector2(max(low.x, min(high.x, val.x), max(low.y, min(high.y, val.y))))
418
+
419
+
420
+ def vfloor(vec: Vector2, inplace: bool = False) -> Vector2:
421
+ if inplace:
422
+ vec.x = math.floor(vec.x)
423
+ vec.y = math.floor(vec.y)
424
+ return vec
425
+ return Vector2(math.floor(vec.x), math.floor(vec.y))
426
+
427
+
428
+ def vceil(vec: Vector2, inplace: bool = False) -> Vector2:
429
+ if inplace:
430
+ vec.x = math.ceil(vec.x)
431
+ vec.y = math.ceil(vec.y)
432
+ return vec
433
+ return Vector2(math.ceil(vec.x), math.ceil(vec.y))