sokoban
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
extends CollisionObject2D

signal level_reset_requested

const GRID_SIZE = 16

var moves = 0 setget set_moves

var last_move = null
var last_move_crate = null
var world: Node2D
var count = 0
var started = false
var won := false

signal won

onready var cam = $Camera2D
onready var tween = $Tween
onready var ray = $RayCast2D
onready var dir = $Direction
onready var anitree: AnimationTree = $AnimationTree
onready var anistate = anitree.get("parameters/playback")


func _ready():
	anitree.active = true


func initialize():
	won = false
	set_physics_process(false)
	yield(get_tree().create_timer(2), "timeout")
	set_physics_process(true)


func _physics_process(_delta):
	if Input.is_action_just_pressed("level_reload"):
		emit_signal("level_reset_requested")
		anistate.travel("Idle")
		set_moves(0)
		last_move = null
		last_move_crate = null
		return

	if not world:
		return

	if Utils.stop_input:
		return

	if get_parent().get_parent().just_started:
		return

	if world.over:
		anistate.travel("Idle")
		return

	if tween.is_active():
		return

	var move_intent = Vector2.ZERO

	move_intent = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")

	if Input.is_action_just_pressed("undo_last_move"):
		if last_move != null:
			self.position -= last_move * GRID_SIZE
			if last_move_crate != null:
				last_move_crate.position -= last_move * GRID_SIZE
			set_moves(moves - 1)
			last_move = null
			last_move_crate = null
		return

	if move_intent.x != 0 && move_intent.y != 0:
		move_intent = Vector2.ZERO

	if move_intent != Vector2.ZERO:
		if not started:
			get_parent().get_parent().start_stopwatch()
			started = true
		var offset = move_intent * GRID_SIZE

		apply_rotation(offset)

		if ray.is_colliding():
			var collider = ray.get_collider()
			if collider.is_in_group("crates"):
				if !collider.push(offset):
					anistate.travel("Idle")
					return

				last_move_crate = collider

			else:
				anistate.travel("Idle")
				return

		else:
			last_move_crate = null

		set_moves(moves + 1)
		last_move = move_intent

		tween.interpolate_property(
			self,
			"position",
			self.position,
			self.position + offset,
			0.35,
			Tween.TRANS_LINEAR,
			Tween.EASE_IN_OUT
		)
		tween.start()

		SoundFx.play("walk", -10, rand_range(.9, 1.1))
		anistate.travel("Run")
		anitree.set("parameters/Run/blend_position", move_intent)
	else:
		anistate.travel("Idle")


func set_moves(new_moves: int):
	moves = new_moves
	$"../../CanvasLayer/HUD/MovesLabel".text = "Moves: " + str(moves)


func apply_rotation(offset: Vector2):
	ray.cast_to = offset

	var new_rot := offset.angle()

	ray.force_raycast_update()

	var future_rot := lerp_angle(dir.rotation, new_rot, 1)

	if future_rot != dir.rotation:
		tween.interpolate_property(
			dir, "rotation", dir.rotation, future_rot, 0.3, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT
		)
		tween.start()


func _over():
	if won:
		return
	emit_signal("won")
	won = true


func _on_Player_body_exited(_body):
	pass
	# if not body.name == "Player" and not body.is_in_group("crates"):
	# 	print("emitting won")
	# 	if won:
	# 		return
	# 	emit_signal("won")
	# 	won = true