TileView.gd 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. @tool
  2. extends Control
  3. signal paste_occurred
  4. signal change_zoom_level(value)
  5. signal terrain_updated(index)
  6. @onready var checkerboard := get_theme_icon("Checkerboard", "EditorIcons")
  7. @onready var paint_symmetry_icons := [
  8. null,
  9. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg"),
  10. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg"),
  11. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg"),
  12. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg"),
  13. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg"),
  14. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg"),
  15. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg"),
  16. preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg"),
  17. ]
  18. # Draw checkerboard and tiles with specific materials in
  19. # individual canvas items via rendering server
  20. var _canvas_item_map = {}
  21. var _canvas_item_background : RID
  22. var tileset: TileSet
  23. var disabled_sources: Array[int] = []: set = set_disabled_sources
  24. var paint := BetterTerrain.TileCategory.NON_TERRAIN
  25. var paint_symmetry := BetterTerrain.SymmetryType.NONE
  26. var highlighted_tile_part := { valid = false }
  27. var zoom_level := 1.0
  28. var tiles_size : Vector2
  29. var tile_size : Vector2i
  30. var tile_part_size : Vector2
  31. var alternate_size : Vector2
  32. var alternate_lookup := []
  33. var initial_click : Vector2i
  34. var prev_position : Vector2i
  35. var current_position : Vector2i
  36. var selection_start : Vector2i
  37. var selection_end : Vector2i
  38. var selection_rect : Rect2i
  39. var selected_tile_states : Array[Dictionary] = []
  40. var copied_tile_states : Array[Dictionary] = []
  41. var staged_paste_tile_states : Array[Dictionary] = []
  42. var pick_icon_terrain : int = -1
  43. var pick_icon_terrain_cancel := false
  44. var undo_manager : EditorUndoRedoManager
  45. var terrain_undo
  46. # Modes for painting
  47. enum PaintMode {
  48. NO_PAINT,
  49. PAINT_TYPE,
  50. PAINT_PEERING,
  51. PAINT_SYMMETRY,
  52. SELECT,
  53. PASTE
  54. }
  55. var paint_mode := PaintMode.NO_PAINT
  56. # Actual interactions for painting
  57. enum PaintAction {
  58. NO_ACTION,
  59. DRAW_TYPE,
  60. ERASE_TYPE,
  61. DRAW_PEERING,
  62. ERASE_PEERING,
  63. DRAW_SYMMETRY,
  64. ERASE_SYMMETRY,
  65. SELECT,
  66. PASTE
  67. }
  68. var paint_action := PaintAction.NO_ACTION
  69. const ALTERNATE_TILE_MARGIN := 18
  70. func _enter_tree() -> void:
  71. _canvas_item_background = RenderingServer.canvas_item_create()
  72. RenderingServer.canvas_item_set_parent(_canvas_item_background, get_canvas_item())
  73. RenderingServer.canvas_item_set_draw_behind_parent(_canvas_item_background, true)
  74. func _exit_tree() -> void:
  75. RenderingServer.free_rid(_canvas_item_background)
  76. for p in _canvas_item_map:
  77. RenderingServer.free_rid(_canvas_item_map[p])
  78. _canvas_item_map.clear()
  79. func refresh_tileset(ts: TileSet) -> void:
  80. tileset = ts
  81. tiles_size = Vector2.ZERO
  82. alternate_size = Vector2.ZERO
  83. alternate_lookup = []
  84. disabled_sources = []
  85. if !tileset:
  86. return
  87. for s in tileset.get_source_count():
  88. var source_id := tileset.get_source_id(s)
  89. var source := tileset.get_source(source_id) as TileSetAtlasSource
  90. if !source or !source.texture:
  91. continue
  92. tiles_size.x = max(tiles_size.x, source.texture.get_width())
  93. tiles_size.y += source.texture.get_height()
  94. tile_size = source.texture_region_size
  95. tile_part_size = Vector2(tile_size) / 3.0
  96. for t in source.get_tiles_count():
  97. var coord := source.get_tile_id(t)
  98. var alt_count := source.get_alternative_tiles_count(coord)
  99. if alt_count <= 1:
  100. continue
  101. var rect := source.get_tile_texture_region(coord, 0)
  102. alternate_lookup.append([rect.size, source_id, coord])
  103. alternate_size.x = max(alternate_size.x, rect.size.x * (alt_count - 1))
  104. alternate_size.y += rect.size.y
  105. _on_zoom_value_changed(zoom_level)
  106. func is_tile_in_source(source: TileSetAtlasSource, coord: Vector2i) -> bool:
  107. var origin := source.get_tile_at_coords(coord)
  108. if origin == Vector2i(-1, -1):
  109. return false
  110. # Animation frames are not needed
  111. var size := source.get_tile_size_in_atlas(origin)
  112. return coord.x < origin.x + size.x and coord.y < origin.y + size.y
  113. func _build_tile_part_from_position(result: Dictionary, position: Vector2i, rect: Rect2) -> void:
  114. result.rect = rect
  115. var type := BetterTerrain.get_tile_terrain_type(result.data)
  116. if type == BetterTerrain.TileCategory.NON_TERRAIN:
  117. return
  118. result.terrain_type = type
  119. var normalize_position := (Vector2(position) - rect.position) / rect.size
  120. var terrain := BetterTerrain.get_terrain(tileset, type)
  121. if !terrain.valid:
  122. return
  123. for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type):
  124. var side_polygon = BetterTerrain.data.peering_polygon(tileset, terrain.type, p)
  125. if Geometry2D.is_point_in_polygon(normalize_position, side_polygon):
  126. result.peering = p
  127. result.polygon = side_polygon
  128. break
  129. func tile_part_from_position(position: Vector2i) -> Dictionary:
  130. if !tileset:
  131. return { valid = false }
  132. var offset := Vector2.ZERO
  133. var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
  134. if Rect2(alt_offset, zoom_level * alternate_size).has_point(position):
  135. for a in alternate_lookup:
  136. if a[1] in disabled_sources:
  137. continue
  138. var next_offset_y = alt_offset.y + zoom_level * a[0].y
  139. if position.y > next_offset_y:
  140. alt_offset.y = next_offset_y
  141. continue
  142. var source := tileset.get_source(a[1]) as TileSetAtlasSource
  143. if !source:
  144. break
  145. var count := source.get_alternative_tiles_count(a[2])
  146. var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1
  147. if index < count:
  148. var alt_id := source.get_alternative_tile_id(a[2], index)
  149. var target_rect := Rect2(
  150. alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x,
  151. zoom_level * a[0]
  152. )
  153. var result := {
  154. valid = true,
  155. source_id = a[1],
  156. coord = a[2],
  157. alternate = alt_id,
  158. data = source.get_tile_data(a[2], alt_id)
  159. }
  160. _build_tile_part_from_position(result, position, target_rect)
  161. return result
  162. else:
  163. for s in tileset.get_source_count():
  164. var source_id := tileset.get_source_id(s)
  165. if source_id in disabled_sources:
  166. continue
  167. var source := tileset.get_source(source_id) as TileSetAtlasSource
  168. if !source || !source.texture:
  169. continue
  170. for t in source.get_tiles_count():
  171. var coord := source.get_tile_id(t)
  172. var rect := source.get_tile_texture_region(coord, 0)
  173. var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
  174. if !target_rect.has_point(position):
  175. continue
  176. var result := {
  177. valid = true,
  178. source_id = source_id,
  179. coord = coord,
  180. alternate = 0,
  181. data = source.get_tile_data(coord, 0)
  182. }
  183. _build_tile_part_from_position(result, position, target_rect)
  184. return result
  185. offset.y += zoom_level * source.texture.get_height()
  186. return { valid = false }
  187. func tile_rect_from_position(position: Vector2i) -> Rect2:
  188. if !tileset:
  189. return Rect2(-1,-1,0,0)
  190. var offset := Vector2.ZERO
  191. var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
  192. if Rect2(alt_offset, zoom_level * alternate_size).has_point(position):
  193. for a in alternate_lookup:
  194. if a[1] in disabled_sources:
  195. continue
  196. var next_offset_y = alt_offset.y + zoom_level * a[0].y
  197. if position.y > next_offset_y:
  198. alt_offset.y = next_offset_y
  199. continue
  200. var source := tileset.get_source(a[1]) as TileSetAtlasSource
  201. if !source:
  202. break
  203. var count := source.get_alternative_tiles_count(a[2])
  204. var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1
  205. if index < count:
  206. var target_rect := Rect2(
  207. alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x,
  208. zoom_level * a[0]
  209. )
  210. return target_rect
  211. else:
  212. for s in tileset.get_source_count():
  213. var source_id := tileset.get_source_id(s)
  214. if source_id in disabled_sources:
  215. continue
  216. var source := tileset.get_source(source_id) as TileSetAtlasSource
  217. if !source:
  218. continue
  219. for t in source.get_tiles_count():
  220. var coord := source.get_tile_id(t)
  221. var rect := source.get_tile_texture_region(coord, 0)
  222. var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
  223. if target_rect.has_point(position):
  224. return target_rect
  225. offset.y += zoom_level * source.texture.get_height()
  226. return Rect2(-1,-1,0,0)
  227. func tile_parts_from_rect(rect:Rect2) -> Array[Dictionary]:
  228. if !tileset:
  229. return []
  230. var tiles:Array[Dictionary] = []
  231. var offset := Vector2.ZERO
  232. var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
  233. for s in tileset.get_source_count():
  234. var source_id := tileset.get_source_id(s)
  235. if source_id in disabled_sources:
  236. continue
  237. var source := tileset.get_source(source_id) as TileSetAtlasSource
  238. if !source:
  239. continue
  240. for t in source.get_tiles_count():
  241. var coord := source.get_tile_id(t)
  242. var tile_rect := source.get_tile_texture_region(coord, 0)
  243. var target_rect := Rect2(offset + zoom_level * tile_rect.position, zoom_level * tile_rect.size)
  244. if target_rect.intersects(rect):
  245. var result := {
  246. valid = true,
  247. source_id = source_id,
  248. coord = coord,
  249. alternate = 0,
  250. data = source.get_tile_data(coord, 0)
  251. }
  252. var pos = target_rect.position + target_rect.size/2
  253. _build_tile_part_from_position(result, pos, target_rect)
  254. tiles.push_back(result)
  255. var alt_count := source.get_alternative_tiles_count(coord)
  256. for a in alt_count:
  257. var alt_id := 0
  258. if a == 0:
  259. continue
  260. target_rect = Rect2(alt_offset + zoom_level * (a - 1) * tile_rect.size.x * Vector2.RIGHT, zoom_level * tile_rect.size)
  261. alt_id = source.get_alternative_tile_id(coord, a)
  262. if target_rect.intersects(rect):
  263. var td := source.get_tile_data(coord, alt_id)
  264. var result := {
  265. valid = true,
  266. source_id = source_id,
  267. coord = coord,
  268. alternate = alt_id,
  269. data = td
  270. }
  271. var pos = target_rect.position + target_rect.size/2
  272. _build_tile_part_from_position(result, pos, target_rect)
  273. tiles.push_back(result)
  274. if alt_count > 1:
  275. alt_offset.y += zoom_level * tile_rect.size.y
  276. offset.y += zoom_level * source.texture.get_height()
  277. return tiles
  278. func _get_canvas_item(td: TileData) -> RID:
  279. if !td.material:
  280. return self.get_canvas_item()
  281. if _canvas_item_map.has(td.material):
  282. return _canvas_item_map[td.material]
  283. var rid = RenderingServer.canvas_item_create()
  284. RenderingServer.canvas_item_set_material(rid, td.material.get_rid())
  285. RenderingServer.canvas_item_set_parent(rid, get_canvas_item())
  286. RenderingServer.canvas_item_set_draw_behind_parent(rid, true)
  287. RenderingServer.canvas_item_set_default_texture_filter(rid, RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_NEAREST)
  288. _canvas_item_map[td.material] = rid
  289. return rid
  290. func _draw_tile_data(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_sides: bool = true) -> void:
  291. var flipped_rect := rect
  292. if td.flip_h:
  293. flipped_rect.size.x = -rect.size.x
  294. if td.flip_v:
  295. flipped_rect.size.y = -rect.size.y
  296. RenderingServer.canvas_item_add_texture_rect_region(
  297. _get_canvas_item(td),
  298. flipped_rect,
  299. texture.get_rid(),
  300. src_rect,
  301. td.modulate,
  302. td.transpose
  303. )
  304. var type := BetterTerrain.get_tile_terrain_type(td)
  305. if type == BetterTerrain.TileCategory.NON_TERRAIN:
  306. draw_rect(rect, Color(0.1, 0.1, 0.1, 0.5), true)
  307. return
  308. var terrain := BetterTerrain.get_terrain(tileset, type)
  309. if !terrain.valid:
  310. return
  311. var transform := Transform2D(0.0, rect.size, 0.0, rect.position)
  312. var center_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, -1)
  313. draw_colored_polygon(center_polygon, Color(terrain.color, 0.6))
  314. if terrain.type == BetterTerrain.TerrainType.DECORATION:
  315. center_polygon.append(center_polygon[0])
  316. draw_polyline(center_polygon, Color.BLACK)
  317. if paint < BetterTerrain.TileCategory.EMPTY or paint >= BetterTerrain.terrain_count(tileset):
  318. return
  319. if not draw_sides:
  320. return
  321. var paint_terrain := BetterTerrain.get_terrain(tileset, paint)
  322. for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type):
  323. if paint in BetterTerrain.tile_peering_types(td, p):
  324. var side_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, p)
  325. draw_colored_polygon(side_polygon, Color(paint_terrain.color, 0.6))
  326. if paint_terrain.type == BetterTerrain.TerrainType.DECORATION:
  327. side_polygon.append(side_polygon[0])
  328. draw_polyline(side_polygon, Color.BLACK)
  329. func _draw_tile_symmetry(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_icon: bool = true) -> void:
  330. var flipped_rect := rect
  331. if td.flip_h:
  332. flipped_rect.size.x = -rect.size.x
  333. if td.flip_v:
  334. flipped_rect.size.y = -rect.size.y
  335. RenderingServer.canvas_item_add_texture_rect_region(
  336. _get_canvas_item(td),
  337. flipped_rect,
  338. texture.get_rid(),
  339. src_rect,
  340. td.modulate,
  341. td.transpose
  342. )
  343. if not draw_icon:
  344. return
  345. var symmetry_type = BetterTerrain.get_tile_symmetry_type(td)
  346. if symmetry_type == 0:
  347. return
  348. var symmetry_icon = paint_symmetry_icons[symmetry_type]
  349. RenderingServer.canvas_item_add_texture_rect_region(
  350. _get_canvas_item(td),
  351. rect,
  352. symmetry_icon.get_rid(),
  353. Rect2(Vector2.ZERO, symmetry_icon.get_size()),
  354. Color(1,1,1,0.5)
  355. )
  356. func _draw() -> void:
  357. if !tileset:
  358. return
  359. # Clear material-based render targets
  360. RenderingServer.canvas_item_clear(_canvas_item_background)
  361. for p in _canvas_item_map:
  362. RenderingServer.canvas_item_clear(_canvas_item_map[p])
  363. var offset := Vector2.ZERO
  364. var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
  365. RenderingServer.canvas_item_add_texture_rect(
  366. _canvas_item_background,
  367. Rect2(alt_offset, zoom_level * alternate_size),
  368. checkerboard.get_rid(),
  369. true
  370. )
  371. for s in tileset.get_source_count():
  372. var source_id := tileset.get_source_id(s)
  373. if source_id in disabled_sources:
  374. continue
  375. var source := tileset.get_source(source_id) as TileSetAtlasSource
  376. if !source or !source.texture:
  377. continue
  378. RenderingServer.canvas_item_add_texture_rect(
  379. _canvas_item_background,
  380. Rect2(offset, zoom_level * source.texture.get_size()),
  381. checkerboard.get_rid(),
  382. true
  383. )
  384. for t in source.get_tiles_count():
  385. var coord := source.get_tile_id(t)
  386. var rect := source.get_tile_texture_region(coord, 0)
  387. var alt_count := source.get_alternative_tiles_count(coord)
  388. var target_rect : Rect2
  389. for a in alt_count:
  390. var alt_id := 0
  391. if a == 0:
  392. target_rect = Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
  393. else:
  394. target_rect = Rect2(alt_offset + zoom_level * (a - 1) * rect.size.x * Vector2.RIGHT, zoom_level * rect.size)
  395. alt_id = source.get_alternative_tile_id(coord, a)
  396. var td := source.get_tile_data(coord, alt_id)
  397. var drawing_current = BetterTerrain.get_tile_terrain_type(td) == paint
  398. if paint_mode == PaintMode.PAINT_SYMMETRY:
  399. _draw_tile_symmetry(source.texture, target_rect, rect, td, drawing_current)
  400. else:
  401. _draw_tile_data(source.texture, target_rect, rect, td)
  402. if drawing_current:
  403. draw_rect(target_rect.grow(-1), Color(0,0,0, 0.75), false, 1)
  404. draw_rect(target_rect, Color(1,1,1, 0.75), false, 1)
  405. if paint_mode == PaintMode.SELECT:
  406. if selected_tile_states.any(func(v):
  407. return v.part.data == td
  408. ):
  409. draw_rect(target_rect.grow(-1), Color.DEEP_SKY_BLUE, false, 2)
  410. if alt_count > 1:
  411. alt_offset.y += zoom_level * rect.size.y
  412. # Blank out unused or uninteresting tiles
  413. var size := source.get_atlas_grid_size()
  414. for y in size.y:
  415. for x in size.x:
  416. var pos := Vector2i(x, y)
  417. if !is_tile_in_source(source, pos):
  418. var atlas_pos := source.margins + pos * (source.separation + source.texture_region_size)
  419. draw_rect(Rect2(offset + zoom_level * atlas_pos, zoom_level * source.texture_region_size), Color(0.0, 0.0, 0.0, 0.8), true)
  420. offset.y += zoom_level * source.texture.get_height()
  421. # Blank out unused alternate tile sections
  422. alt_offset = Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
  423. for a in alternate_lookup:
  424. if a[1] in disabled_sources:
  425. continue
  426. var source := tileset.get_source(a[1]) as TileSetAtlasSource
  427. if source:
  428. var count := source.get_alternative_tiles_count(a[2]) - 1
  429. var occupied_width = count * zoom_level * a[0].x
  430. var area := Rect2(
  431. alt_offset.x + occupied_width,
  432. alt_offset.y,
  433. zoom_level * alternate_size.x - occupied_width,
  434. zoom_level * a[0].y
  435. )
  436. draw_rect(area, Color(0.0, 0.0, 0.0, 0.8), true)
  437. alt_offset.y += zoom_level * a[0].y
  438. if highlighted_tile_part.valid:
  439. if paint_mode == PaintMode.PAINT_PEERING and highlighted_tile_part.has("polygon"):
  440. var transform := Transform2D(0.0, highlighted_tile_part.rect.size - 2 * Vector2.ONE, 0.0, highlighted_tile_part.rect.position + Vector2.ONE)
  441. draw_colored_polygon(transform * highlighted_tile_part.polygon, Color(Color.WHITE, 0.2))
  442. if paint_mode != PaintMode.NO_PAINT:
  443. var inner_rect := Rect2(highlighted_tile_part.rect.position + Vector2.ONE, highlighted_tile_part.rect.size - 2 * Vector2.ONE)
  444. draw_rect(inner_rect, Color.WHITE, false)
  445. if paint_mode == PaintMode.PAINT_SYMMETRY:
  446. if paint_symmetry > 0:
  447. var symmetry_icon = paint_symmetry_icons[paint_symmetry]
  448. draw_texture_rect(symmetry_icon, highlighted_tile_part.rect, false, Color(0.5,0.75,1,0.5))
  449. if paint_mode == PaintMode.SELECT:
  450. draw_rect(selection_rect, Color.WHITE, false)
  451. if paint_mode == PaintMode.PASTE:
  452. if staged_paste_tile_states.size() > 0:
  453. var base_rect = staged_paste_tile_states[0].base_rect
  454. var paint_terrain := BetterTerrain.get_terrain(tileset, paint)
  455. var paint_terrain_type = paint_terrain.type
  456. if paint_terrain_type == BetterTerrain.TerrainType.CATEGORY:
  457. paint_terrain_type = 0
  458. for state in staged_paste_tile_states:
  459. var staged_rect:Rect2 = state.base_rect
  460. staged_rect.position -= base_rect.position + base_rect.size / 2
  461. staged_rect.position *= zoom_level
  462. staged_rect.size *= zoom_level
  463. staged_rect.position += Vector2(current_position)
  464. var real_rect = tile_rect_from_position(staged_rect.get_center())
  465. if real_rect.position.x >= 0:
  466. draw_rect(real_rect, Color(0,0,0, 0.3), true)
  467. var transform := Transform2D(0.0, real_rect.size, 0.0, real_rect.position)
  468. var tile_sides = BetterTerrain.data.get_terrain_peering_cells(tileset, paint_terrain_type)
  469. for p in tile_sides:
  470. if state.paint in BetterTerrain.tile_peering_types(state.part.data, p):
  471. var side_polygon = BetterTerrain.data.peering_polygon(tileset, paint_terrain_type, p)
  472. var color = Color(paint_terrain.color, 0.6)
  473. draw_colored_polygon(transform * side_polygon, color)
  474. draw_rect(staged_rect, Color.DEEP_PINK, false)
  475. func delete_selection():
  476. undo_manager.create_action("Delete tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset)
  477. for t in selected_tile_states:
  478. for side in range(16):
  479. var old_peering = BetterTerrain.tile_peering_types(t.part.data, side)
  480. if old_peering.has(paint):
  481. undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, t.part.data, side, paint)
  482. undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, t.part.data, side, paint)
  483. undo_manager.add_do_method(self, &"queue_redraw")
  484. undo_manager.add_undo_method(self, &"queue_redraw")
  485. undo_manager.commit_action()
  486. func toggle_selection():
  487. undo_manager.create_action("Toggle tile terrain", UndoRedo.MERGE_DISABLE, tileset, true)
  488. for t in selected_tile_states:
  489. var type := BetterTerrain.get_tile_terrain_type(t.part.data)
  490. var goal := paint if paint != type else BetterTerrain.TileCategory.NON_TERRAIN
  491. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, t.part.data, goal])
  492. if goal == BetterTerrain.TileCategory.NON_TERRAIN:
  493. terrain_undo.create_peering_restore_point_tile(
  494. undo_manager,
  495. tileset,
  496. t.part.source_id,
  497. t.part.coord,
  498. t.part.alternate
  499. )
  500. else:
  501. undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, t.part.data, type)
  502. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  503. undo_manager.add_undo_method(self, &"queue_redraw")
  504. undo_manager.commit_action()
  505. terrain_undo.action_count += 1
  506. func copy_selection():
  507. copied_tile_states = selected_tile_states
  508. func paste_selection():
  509. staged_paste_tile_states = copied_tile_states
  510. selected_tile_states = []
  511. paint_mode = PaintMode.PASTE
  512. paint_action = PaintAction.PASTE
  513. paste_occurred.emit()
  514. queue_redraw()
  515. func set_disabled_sources(list):
  516. disabled_sources = list
  517. queue_redraw()
  518. func emit_terrain_updated(index):
  519. terrain_updated.emit(index)
  520. func _gui_input(event) -> void:
  521. if event is InputEventKey and event.is_pressed():
  522. if event.keycode == KEY_DELETE and not event.echo:
  523. accept_event()
  524. delete_selection()
  525. if event.keycode == KEY_ENTER and not event.echo:
  526. accept_event()
  527. toggle_selection()
  528. if event.keycode == KEY_ESCAPE and not event.echo:
  529. accept_event()
  530. if paint_action == PaintAction.PASTE:
  531. staged_paste_tile_states = []
  532. paint_mode = PaintMode.SELECT
  533. paint_action = PaintAction.NO_ACTION
  534. selection_start = Vector2i(-1,-1)
  535. if event.keycode == KEY_C and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
  536. accept_event()
  537. copy_selection()
  538. if event.keycode == KEY_X and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
  539. accept_event()
  540. copy_selection()
  541. delete_selection()
  542. if event.keycode == KEY_V and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
  543. accept_event()
  544. paste_selection()
  545. if event is InputEventMouseButton:
  546. if event.button_index == MOUSE_BUTTON_WHEEL_UP and (event.ctrl_pressed or event.meta_pressed):
  547. accept_event()
  548. change_zoom_level.emit(zoom_level * 1.1)
  549. if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and (event.ctrl_pressed or event.meta_pressed):
  550. accept_event()
  551. change_zoom_level.emit(zoom_level / 1.1)
  552. var released : bool = event is InputEventMouseButton and (not event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT))
  553. if released:
  554. paint_action = PaintAction.NO_ACTION
  555. if event is InputEventMouseMotion:
  556. prev_position = current_position
  557. current_position = event.position
  558. var tile := tile_part_from_position(event.position)
  559. if tile.valid != highlighted_tile_part.valid or\
  560. (tile.valid and tile.data != highlighted_tile_part.data) or\
  561. (tile.valid and tile.get("peering") != highlighted_tile_part.get("peering")) or\
  562. event.button_mask & MOUSE_BUTTON_LEFT and paint_action == PaintAction.SELECT:
  563. queue_redraw()
  564. highlighted_tile_part = tile
  565. var clicked : bool = event is InputEventMouseButton and (event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT))
  566. if clicked:
  567. initial_click = current_position
  568. selection_start = Vector2i(-1,-1)
  569. terrain_undo.action_index += 1
  570. terrain_undo.action_count = 0
  571. if released:
  572. terrain_undo.finish_action()
  573. selection_rect = Rect2i(0,0,0,0)
  574. queue_redraw()
  575. if paint_action == PaintAction.PASTE:
  576. if event is InputEventMouseMotion:
  577. queue_redraw()
  578. if clicked:
  579. if event.button_index == MOUSE_BUTTON_LEFT and staged_paste_tile_states.size() > 0:
  580. undo_manager.create_action("Paste tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset)
  581. var base_rect = staged_paste_tile_states[0].base_rect
  582. for p in staged_paste_tile_states:
  583. var staged_rect:Rect2 = p.base_rect
  584. staged_rect.position -= base_rect.position + base_rect.size / 2
  585. staged_rect.position *= zoom_level
  586. staged_rect.size *= zoom_level
  587. staged_rect.position += Vector2(current_position)
  588. var old_tile_part = tile_part_from_position(staged_rect.get_center())
  589. var new_tile_state = p
  590. if (not old_tile_part.valid) or (not new_tile_state.part.valid):
  591. continue
  592. for side in range(16):
  593. var old_peering = BetterTerrain.tile_peering_types(old_tile_part.data, side)
  594. var new_sides = new_tile_state.sides
  595. if new_sides.has(side) and not old_peering.has(paint):
  596. undo_manager.add_do_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint)
  597. undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint)
  598. elif old_peering.has(paint) and not new_sides.has(side):
  599. undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint)
  600. undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint)
  601. var old_symmetry = BetterTerrain.get_tile_symmetry_type(old_tile_part.data)
  602. var new_symmetry = new_tile_state.symmetry
  603. if new_symmetry != old_symmetry:
  604. undo_manager.add_do_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, new_symmetry)
  605. undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, old_symmetry)
  606. undo_manager.add_do_method(self, &"queue_redraw")
  607. undo_manager.add_undo_method(self, &"queue_redraw")
  608. undo_manager.commit_action()
  609. staged_paste_tile_states = []
  610. paint_mode = PaintMode.SELECT
  611. paint_action = PaintAction.SELECT
  612. return
  613. if clicked and pick_icon_terrain >= 0:
  614. highlighted_tile_part = tile_part_from_position(current_position)
  615. if !highlighted_tile_part.valid:
  616. return
  617. var t = BetterTerrain.get_terrain(tileset, paint)
  618. var prev_icon = t.icon.duplicate()
  619. var icon = {
  620. source_id = highlighted_tile_part.source_id,
  621. coord = highlighted_tile_part.coord
  622. }
  623. undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
  624. undo_manager.add_do_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, icon)
  625. undo_manager.add_do_method(self, &"emit_terrain_updated", paint)
  626. undo_manager.add_undo_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, prev_icon)
  627. undo_manager.add_undo_method(self, &"emit_terrain_updated", paint)
  628. undo_manager.commit_action()
  629. pick_icon_terrain = -1
  630. return
  631. if pick_icon_terrain_cancel:
  632. pick_icon_terrain = -1
  633. pick_icon_terrain_cancel = false
  634. if paint != BetterTerrain.TileCategory.NON_TERRAIN and clicked:
  635. paint_action = PaintAction.NO_ACTION
  636. if highlighted_tile_part.valid:
  637. match [paint_mode, event.button_index]:
  638. [PaintMode.PAINT_TYPE, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_TYPE
  639. [PaintMode.PAINT_TYPE, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_TYPE
  640. [PaintMode.PAINT_PEERING, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_PEERING
  641. [PaintMode.PAINT_PEERING, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_PEERING
  642. [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_SYMMETRY
  643. [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_SYMMETRY
  644. [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT
  645. else:
  646. match [paint_mode, event.button_index]:
  647. [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT
  648. if (clicked or event is InputEventMouseMotion) and paint_action != PaintAction.NO_ACTION:
  649. if paint_action == PaintAction.SELECT:
  650. if clicked:
  651. selection_start = Vector2i(-1,-1)
  652. queue_redraw()
  653. if selection_start.x < 0:
  654. selection_start = current_position
  655. selection_end = current_position
  656. selection_rect = Rect2i(selection_start, selection_end - selection_start).abs()
  657. var selected_tile_parts = tile_parts_from_rect(selection_rect)
  658. selected_tile_states = []
  659. for t in selected_tile_parts:
  660. var state := {
  661. part = t,
  662. base_rect = Rect2(t.rect.position / zoom_level, t.rect.size / zoom_level),
  663. paint = paint,
  664. sides = BetterTerrain.tile_peering_for_type(t.data, paint),
  665. symmetry = BetterTerrain.get_tile_symmetry_type(t.data)
  666. }
  667. selected_tile_states.push_back(state)
  668. else:
  669. if !highlighted_tile_part.valid:
  670. return
  671. #slightly crude and non-optimal but way simpler than the "correct" solution
  672. var current_position_vec2 = Vector2(current_position)
  673. var prev_position_vec2 = Vector2(prev_position)
  674. var mouse_dist = current_position_vec2.distance_to(prev_position_vec2)
  675. var step_size = (tile_part_size.x * zoom_level)
  676. var steps = ceil(mouse_dist / step_size) + 1
  677. for i in range(steps):
  678. var t = float(i) / steps
  679. var check_position = prev_position_vec2.lerp(current_position_vec2, t)
  680. highlighted_tile_part = tile_part_from_position(check_position)
  681. if !highlighted_tile_part.valid:
  682. continue
  683. if paint_action == PaintAction.DRAW_TYPE or paint_action == PaintAction.ERASE_TYPE:
  684. var type := BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data)
  685. var goal := paint if paint_action == PaintAction.DRAW_TYPE else BetterTerrain.TileCategory.NON_TERRAIN
  686. if type != goal:
  687. undo_manager.create_action("Set tile terrain type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
  688. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, highlighted_tile_part.data, goal])
  689. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  690. if goal == BetterTerrain.TileCategory.NON_TERRAIN:
  691. terrain_undo.create_peering_restore_point_tile(
  692. undo_manager,
  693. tileset,
  694. highlighted_tile_part.source_id,
  695. highlighted_tile_part.coord,
  696. highlighted_tile_part.alternate
  697. )
  698. else:
  699. undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, highlighted_tile_part.data, type)
  700. undo_manager.add_undo_method(self, &"queue_redraw")
  701. undo_manager.commit_action()
  702. terrain_undo.action_count += 1
  703. elif paint_action == PaintAction.DRAW_PEERING:
  704. if highlighted_tile_part.has("peering"):
  705. if !(paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering)):
  706. undo_manager.create_action("Set tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
  707. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"add_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint])
  708. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  709. undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint)
  710. undo_manager.add_undo_method(self, &"queue_redraw")
  711. undo_manager.commit_action()
  712. terrain_undo.action_count += 1
  713. elif paint_action == PaintAction.ERASE_PEERING:
  714. if highlighted_tile_part.has("peering"):
  715. if paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering):
  716. undo_manager.create_action("Remove tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
  717. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"remove_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint])
  718. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  719. undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint)
  720. undo_manager.add_undo_method(self, &"queue_redraw")
  721. undo_manager.commit_action()
  722. terrain_undo.action_count += 1
  723. elif paint_action == PaintAction.DRAW_SYMMETRY:
  724. if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data):
  725. undo_manager.create_action("Set tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
  726. var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data)
  727. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, paint_symmetry])
  728. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  729. undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry)
  730. undo_manager.add_undo_method(self, &"queue_redraw")
  731. undo_manager.commit_action()
  732. terrain_undo.action_count += 1
  733. elif paint_action == PaintAction.ERASE_SYMMETRY:
  734. if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data):
  735. undo_manager.create_action("Remove tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
  736. var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data)
  737. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, BetterTerrain.SymmetryType.NONE])
  738. terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
  739. undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry)
  740. undo_manager.add_undo_method(self, &"queue_redraw")
  741. undo_manager.commit_action()
  742. terrain_undo.action_count += 1
  743. func _on_zoom_value_changed(value) -> void:
  744. zoom_level = value
  745. custom_minimum_size.x = zoom_level * tiles_size.x
  746. if alternate_size.x > 0:
  747. custom_minimum_size.x += ALTERNATE_TILE_MARGIN + zoom_level * alternate_size.x
  748. custom_minimum_size.y = zoom_level * max(tiles_size.y, alternate_size.y)
  749. queue_redraw()
  750. func clear_highlighted_tile() -> void:
  751. highlighted_tile_part = { valid = false }
  752. queue_redraw()