You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
5.4 KiB
168 lines
5.4 KiB
class_name Shape |
|
# FIXME: Potential incompatibility between different game versions that have different shapes. |
|
|
|
static var REGISTRY: Array[Shape] |
|
static func lookup(id: int) -> Shape: |
|
return REGISTRY[id] if (id >= 0) and (id < REGISTRY.size()) else EMPTY |
|
|
|
# Initialized in `Base._static_init`. |
|
static var EMPTY : Shape |
|
static var FULL : Shape |
|
|
|
static var DEFAULT : Shape |
|
|
|
var id : int |
|
var base : Base |
|
var mirror : bool |
|
var angle : int |
|
var shape : Shape2D |
|
var points : PackedVector2Array |
|
var uvs : PackedVector2Array |
|
|
|
func _init(base: Base, mirror: bool, angle: int, shape: Shape2D, points: PackedVector2Array) -> void: |
|
id = REGISTRY.size() |
|
REGISTRY.append(self) |
|
|
|
self.base = base |
|
self.mirror = mirror |
|
self.angle = angle |
|
self.shape = shape |
|
self.points = points |
|
|
|
# TODO: Define UVs properly. |
|
for point in points: |
|
uvs.append((Vector2.ONE / 2) + point) |
|
|
|
|
|
class Base: |
|
static var EMPTY := Base.new("empty") |
|
static var FULL := Base.new("full" , _rect()) |
|
static var HALF := Base.new("half" , [ 0,1, 2,1, 2,2, 0,2 ], true) |
|
static var SLOPE := Base.new("slope" , [ 0,1, 1,0, 1,1 ], true) |
|
static var HALF_SLOPE := Base.new("half_slope" , [ 0,2, 1,0, 2,2 ], true, true) |
|
static var HALF_SLOPE_BIG := Base.new("half_slope_big", [ 0,1, 2,0, 2,2, 0,2 ], true, true) |
|
|
|
static func _static_init() -> void: |
|
Shape.EMPTY = EMPTY.variants[0] |
|
Shape.FULL = FULL .variants[0] |
|
Shape.DEFAULT = Shape.EMPTY |
|
|
|
var name : String |
|
var variants : Array[Shape] |
|
|
|
func _init(name: String, shape = null, rotated := false, mirrored := false) -> void: |
|
self.name = name |
|
if shape == null: |
|
variants.append(Shape.new(self, false, 0, null, PackedVector2Array())) |
|
elif shape is RectangleShape2D: |
|
variants.append(Shape.new(self, false, 0, shape, _points(shape))) |
|
if rotated: |
|
shape = _rotate(shape) # rotate by 90 degrees (swap x and y components) |
|
variants.append(Shape.new(self, false, 0, shape, _points(shape))) |
|
elif shape is Array: |
|
# Convert points (pairs of ints) in array to convex shape. |
|
var factor := float(shape.max()) |
|
var points := PackedVector2Array() |
|
for i in range(0, shape.size(), 2): |
|
var point := Vector2(shape[i] / factor, shape[i + 1] / factor) |
|
points.append(point - (Vector2.ONE / 2)) # ensure shape is centered |
|
|
|
# Add variations of the shape (rotated, mirrored), including the standard shape. |
|
for mirror in [ false, true ] if mirrored else [ false ]: |
|
if mirror: |
|
# Mirror (flip) points along the X axis. |
|
points = points.duplicate() |
|
for i in points.size(): |
|
points[i] = points[i] * Transform2D.FLIP_X |
|
|
|
for angle in [ 0, 90, 180, 270 ] if rotated else [ 0 ]: |
|
if angle != 0: |
|
# Rotate points around the center. |
|
var transform := Transform2D(deg_to_rad(angle), Vector2.ZERO) |
|
points = points.duplicate() |
|
for i in points.size(): |
|
points[i] = points[i] * transform |
|
|
|
var shape_points: PackedVector2Array |
|
for point in points: shape_points.append(point * Block.SIZE) |
|
shape = ConvexPolygonShape2D.new() |
|
shape.points = shape_points |
|
|
|
variants.append(Shape.new(self, mirror, angle, shape, points)) |
|
else: |
|
breakpoint |
|
|
|
static func _rect(w := 1.0, h := 1.0) -> RectangleShape2D: |
|
var rect := RectangleShape2D.new() |
|
rect.size = Vector2(w, h) * Block.SIZE |
|
return rect |
|
static func _rotate(rect: RectangleShape2D) -> RectangleShape2D: |
|
return _rect(rect.size.y, rect.size.x) |
|
static func _points(rect: RectangleShape2D) -> PackedVector2Array: |
|
var w := rect.size.x / Block.SIZE / 2 |
|
var h := rect.size.y / Block.SIZE / 2 |
|
return [ Vector2(-w, -h), Vector2(w, -h), Vector2(w, h), Vector2(-w, h) ] |
|
|
|
|
|
class Layer extends StaticBody2D: |
|
var data: PackedByteArray |
|
|
|
var chunk: Chunk: |
|
get: return get_parent() |
|
|
|
func _init() -> void: |
|
data.resize(Chunk.SIZE * Chunk.SIZE) |
|
|
|
static func load(buffer: StreamBuffer) -> Layer: |
|
var result := Layer.new() |
|
buffer.read_raw_buffer_into(result.data) |
|
return result |
|
|
|
func save(buffer: StreamBuffer) -> void: |
|
buffer.write_raw_buffer(data) |
|
|
|
func get_at(pos: Vector2i) -> Shape: |
|
var index := Chunk.array_index(pos) |
|
return Shape.lookup(data[index]) |
|
|
|
func set_at(pos: Vector2i, value: Shape) -> void: |
|
set_id_at(pos, value.id) |
|
@rpc("reliable") |
|
func set_id_at(pos: Vector2i, id: int) -> void: |
|
var index := Chunk.array_index(pos) |
|
if data[index] != id: |
|
# TODO: Keep track of how many non-air blocks are present, to allow for cleaning empty chunks. |
|
data[index] = id |
|
chunk.dirty = true |
|
|
|
# Send change to every player tracking this chunk. |
|
if multiplayer.is_server(): |
|
for player in chunk.get_players_tracking(): |
|
if player.network.is_local: continue # skip server player |
|
set_id_at.rpc_id(player.network.peer_id, pos, id) |
|
|
|
|
|
func _ready() -> void: |
|
# TODO: Only update if shapes have been changed. |
|
chunk.clean.connect(_update_shapes) |
|
_update_shapes() |
|
|
|
func _update_shapes() -> void: |
|
const CHUNK_HALF_SIZE := (Vector2.ONE * Chunk.SIZE * Block.SIZE) / 2 |
|
|
|
# FIXME: Updating the shape like this is not ideal. |
|
for child in get_children(): |
|
if child is CollisionShape2D: |
|
child.queue_free() |
|
|
|
for x in Chunk.SIZE: |
|
for y in Chunk.SIZE: |
|
var pos := Vector2i(x, y) |
|
var shape := get_at(pos) |
|
if shape.shape == null: continue |
|
|
|
var shape2d := CollisionShape2D.new() |
|
shape2d.name = "Block %s" % pos |
|
shape2d.position = (Vector2(pos) + (Vector2.ONE / 2)) * Block.SIZE - CHUNK_HALF_SIZE |
|
shape2d.shape = shape.shape |
|
add_child(shape2d)
|
|
|