sokoban
Diffstat (limited to 'Level.gd')
| -rw-r--r-- | Level.gd | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/Level.gd b/Level.gd new file mode 100644 index 0000000..df597f8 --- /dev/null +++ b/Level.gd @@ -0,0 +1,304 @@ +extends Node2D + +const GRID_SIZE = 16 +const grassDecorationIds = [0, 1, 2, 3, 4, 5, 6, 7] +const treeDecorationIds = [8, 9, 10, 11] +const stoneDecorationIds = [12, 13, 14, 15, 16] +const mushroomDecorationIds = [17, 18, 19, 20, 21, 22, 23, 24] +var explosionEffect = preload("res://ExplosionEffect.tscn") + +var thread: Thread +var wall_positions: PoolVector2Array = [] +var crate_prefab = preload("res://Crate.tscn") +var target_prefab = preload("res://Target.tscn") + +var game_over := false + +onready var crates = $LevelContainer/Crates +onready var player = $LevelContainer/Player +onready var targets = $LevelContainer/Targets +onready var walls = $LevelContainer/Walls +onready var floors = $LevelContainer/Floors +onready var timer = $Timer +onready var others = $LevelContainer/Others +onready var cam = $LevelContainer/Player/Camera2D +var consol + +var current_level := "" +onready var tilemaps = [walls, others, floors] +var just_started = true + +var level_size = Vector2(0, 0) + +signal game_over +signal level_completed(completed) +signal level_reset +signal level_made + + +func _ready(): + player.connect("level_reset_requested", self, "_on_Player_level_reset_requested") + thread = Thread.new() + reset_time() + + +func reset_time(): + MainInstances.stopwatch.reset() + player.started = false + + +func start_stopwatch(): + MainInstances.stopwatch.start() + + +func load_level(level: String, decorate = true): + $LevelContainer/Walls.modulate = Color.white + $LevelContainer/Player/RayCast2D.set_collision_mask_bit(0, true) + if thread.is_alive(): + return + if thread.is_active(): + thread.wait_to_finish() + reset_time() + consol = MainInstances.console + if decorate: + consol.Log("Generating level " + level, .5, .5) + SaveLoad.files.level.data.current_level = level + SaveLoad.save("level") + thread.start(self, "level_load", [level, decorate]) + + +func level_load(level: Array): + just_started = true + player.set_moves(0) + current_level = level[0] + call_deferred("_reset_level", level[1]) + + +func _exit_tree(): + if thread.is_active(): + thread.wait_to_finish() + + +func _reset_level(decorate): + game_over = false + player.initialize() + delete_children(crates) + if decorate: + walls.clear() + others.clear() + delete_children(floors) + delete_children(targets) + + if current_level == "": + return + + var file = File.new() + file.open("res://Levels/%s.sokolvl" % current_level, File.READ) + + var version = 0 + var row = 0 + var player_pos + level_size = Vector2(0, 0) + + while !file.eof_reached(): + var line = file.get_line() + if line.begins_with(";"): + var meta = line.split(": ", false, 1) + if meta[0] == ";version": + version = int(meta[1]) + elif line != "": + if version != 1: + push_error("Not supported .sokolvl version: " + str(version)) + return + var col = 0 + + for x in line: + var tile_pos = Vector2(col, row) * GRID_SIZE + + if x == "#": + if decorate: + add_wall(tile_pos) + if x in [".", "X", "O", "@", "%", "A"]: + if decorate: + add_floor(tile_pos) + if x in ["@", "A"]: + player_pos = tile_pos + if x in ["X", "%"]: + add_crate(tile_pos) + if x in ["O", "%", "A"]: + if decorate: + add_target(tile_pos) + + col += 1 + row += 1 + level_size.y += 1 + level_size.x = max(level_size.x, col) + + file.close() + + $CanvasLayer/HUD/LevelLabel.text = "Level = %s" % current_level + + var new_zoom = .5 + new_zoom = clamp(new_zoom, get_parent().min_zoom, get_parent().max_zoom) + new_zoom = Vector2(new_zoom, new_zoom) + var level_int + if level_size.x > level_size.y: + level_int = level_size.x + else: + level_int = level_size.y + + new_zoom += Vector2(level_int / 45, level_int / 45) + $Tween.interpolate_property( + cam, "zoom", cam.zoom, new_zoom, 2, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT + ) + $Tween.start() + timer.start(2) + if decorate: + decorate(-50, 50) + initialize_player(player_pos) + Utils.unload_loading_screen() + yield(timer, "timeout") + just_started = false + emit_signal("level_made") + return + + +static func delete_children(node): + for n in node.get_children(): + node.remove_child(n) + n.queue_free() + + +func decorate(x, y): + for tile in check_for_empty_tile(Vector2(x, y)): + match randi() % 101: + 1: + add_mushroom(tile) + 2: + add_rock(tile) + 3: + add_tree(tile) + + +func _on_Crate_target_updated(): + var crates_in_place = 0 + + for crate in crates.get_children(): + if crate.target_count > 0: + crates_in_place += 1 + + if crates_in_place == crates.get_child_count(): + emit_signal("level_completed") + + +func _on_Player_level_reset_requested(): + if player.tween.is_active(): + yield(player.tween, "tween_all_completed") + load_level(current_level, false) + emit_signal("level_reset") + + +func _on_Crate_game_over(): + if not game_over: + game_over = true + emit_signal("game_over") + + +func add_target(tile_pos): + var target = target_prefab.instance() + target.main = self + target.position = tile_pos + targets.add_child(target) + + +func add_crate(tile_pos): + var crate = crate_prefab.instance() + crate.position = tile_pos + crate.main = get_parent() + crate.connect("target_updated", self, "_on_Crate_target_updated") + crate.connect("game_over_detected", self, "_on_Crate_game_over") + crates.add_child(crate) + + +func initialize_player(tile_pos): + player.position = tile_pos + player.world = get_parent() + + +func add_floor(tile_pos): + floors.set_cellv(tile_pos / 16, 0) + randomize() + if randi() % 5 == 2: + others.set_cellv(tile_pos / 16, grassDecorationIds[randi() % grassDecorationIds.size()]) + + +func add_wall(tile_pos): + wall_positions.append(tile_pos) + walls.set_cellv(tile_pos / 16, 1) + walls.update_bitmask_area(tile_pos / 16) + + +func check_for_empty_tile(size: Vector2 = Vector2(-75, 75)): + var empty_tiles: PoolVector2Array = [] + for x in range(size.x, size.y): + for y in range(size.x, size.y): + var tile_pos = Vector2(x, y) + var lower_tile_pos = tile_pos + var left_tile_pos = tile_pos + var right_tile_pos = tile_pos + var up_tile_pos = tile_pos + var down_right_tile_pos = tile_pos + var down_left_tile_pos = tile_pos + down_right_tile_pos += Vector2.DOWN + Vector2.RIGHT + down_left_tile_pos += Vector2.DOWN + Vector2.LEFT + lower_tile_pos += Vector2.DOWN + left_tile_pos += Vector2.LEFT + right_tile_pos += Vector2.RIGHT + up_tile_pos += Vector2.UP + var tile_positions = [ + down_left_tile_pos, + down_right_tile_pos, + lower_tile_pos, + left_tile_pos, + right_tile_pos, + up_tile_pos, + tile_pos + ] + var count2 := 0 + for tile in tile_positions: + var count := 0 + for tilemap in tilemaps: + if empty(tilemap, tile): + count += 1 + if count == tilemaps.size(): + count2 += 1 + if count2 == tile_positions.size(): + empty_tiles.append(tile_pos) + return empty_tiles + + +func empty(tilemap, tile) -> bool: + if tilemap.get_cellv(tile) != -1: + return false + return true + + +func add_tree(tile): + others.set_cellv(tile, treeDecorationIds[randi() % treeDecorationIds.size()]) + + +func add_rock(tile): + others.set_cellv(tile, stoneDecorationIds[randi() % stoneDecorationIds.size()]) + + +func add_mushroom(tile): + others.set_cellv(tile, mushroomDecorationIds[randi() % mushroomDecorationIds.size()]) + + +func explode_walls(): + for positions in wall_positions: + Utils.instance_scene_on_main(positions, explosionEffect) + + +func _on_Player_won(): + emit_signal("level_completed", true) |