6
6
7
7
package com.almasb.fxgl.quest
8
8
9
+
import com.almasb.fxgl.core.Updatable
9
10
import com.almasb.fxgl.core.collection.PropertyMap
10
11
import com.almasb.fxgl.logging.Logger
11
12
import javafx.beans.binding.Bindings
@@ -27,10 +28,19 @@ import java.util.concurrent.Callable
27
28
*
28
29
* @author Almas Baimagambetov (almaslvl@gmail.com)
29
30
*/
30
-
class Quest(val name: String) {
31
+
class Quest
32
+
@JvmOverloads constructor(name: String, val vars: PropertyMap = PropertyMap()) : Updatable {
31
33
32
34
private val log = Logger.get(javaClass)
33
35
36
+
private val nameProp = SimpleStringProperty(name)
37
+
38
+
var name: String
39
+
get() = nameProp.value
40
+
set(value) { nameProp.value = value }
41
+
42
+
fun nameProperty() = nameProp
43
+
34
44
private val objectives = FXCollections.observableArrayList<QuestObjective>()
35
45
private val objectivesReadOnly = FXCollections.unmodifiableObservableList(objectives)
36
46
@@ -49,49 +59,66 @@ class Quest(val name: String) {
49
59
/**
50
60
* @return true if any of the states apart from NOT_STARTED
51
61
*/
52
-
val hasStarted: Boolean
62
+
val isStarted: Boolean
53
63
get() = state != QuestState.NOT_STARTED
54
64
55
65
@JvmOverloads fun addIntObjective(desc: String, varName: String, varValue: Int, duration: Duration = Duration.ZERO): QuestObjective {
56
-
return IntQuestObjective(desc, varName, varValue, duration).also { addObjective(it) }
66
+
return IntQuestObjective(desc, vars, varName, varValue, duration).also { addObjective(it) }
57
67
}
58
68
59
69
@JvmOverloads fun addBooleanObjective(desc: String, varName: String, varValue: Boolean, duration: Duration = Duration.ZERO): QuestObjective {
60
-
return BooleanQuestObjective(desc, varName, varValue, duration).also { addObjective(it) }
70
+
return BooleanQuestObjective(desc, vars, varName, varValue, duration).also { addObjective(it) }
61
71
}
62
72
63
73
private fun addObjective(objective: QuestObjective) {
64
74
objectives += objective
65
75
66
-
if (hasStarted)
76
+
if (isStarted)
67
77
rebindStateToObjectives()
68
78
}
69
79
70
80
fun removeObjective(objective: QuestObjective) {
71
81
objectives -= objective
72
82
73
-
if (hasStarted)
83
+
if (isStarted)
74
84
rebindStateToObjectives()
75
85
}
76
86
77
87
/**
78
88
* Can only be called from NOT_STARTED state.
89
+
* Binds quest state to the combined state of its objectives.
79
90
*/
80
91
internal fun start() {
81
92
if (objectives.isEmpty()) {
82
93
log.warning("Cannot start quest $name because it has no objectives")
83
94
return
84
95
}
85
96
86
-
if (hasStarted) {
97
+
if (isStarted) {
87
98
log.warning("Cannot start quest $name because it has already been started")
88
99
return
89
100
}
90
101
91
102
rebindStateToObjectives()
92
103
}
93
104
105
+
override fun onUpdate(tpf: Double) {
106
+
objectives.forEach { it.onUpdate(tpf) }
107
+
}
108
+
109
+
/**
110
+
* Sets the state to NOT_STARTED and unbinds objectives from the variables they are tracking.
111
+
*/
112
+
internal fun stop() {
113
+
stateProp.unbind()
114
+
stateProp.value = QuestState.NOT_STARTED
115
+
116
+
objectives.forEach { it.unbindFromVars() }
117
+
}
118
+
94
119
private fun rebindStateToObjectives() {
120
+
objectives.forEach { it.bindToVars() }
121
+
95
122
val failedBinding = objectives.map { it.stateProperty() }
96
123
.foldRight(Bindings.createBooleanBinding(Callable { false })) { state, binding ->
97
124
state.isEqualTo(QuestState.FAILED).or(binding)
@@ -126,11 +153,16 @@ constructor(
126
153
*/
127
154
val description: String,
128
155
156
+
/**
157
+
* Variables map, from which to check whether the objective is complete.
158
+
*/
159
+
protected val vars: PropertyMap,
160
+
129
161
/**
130
162
* How much time is given to complete this objective.
131
163
* Default: 0 - unlimited.
132
164
*/
133
-
val expireDuration: Duration = Duration.ZERO) {
165
+
val expireDuration: Duration = Duration.ZERO) : Updatable {
134
166
135
167
private val stateProp = ReadOnlyObjectWrapper(QuestState.ACTIVE)
136
168
@@ -139,6 +171,17 @@ constructor(
139
171
140
172
fun stateProperty(): ReadOnlyObjectProperty<QuestState> = stateProp.readOnlyProperty
141
173
174
+
private val timeRemainingProp = ReadOnlyDoubleWrapper(expireDuration.toSeconds())
175
+
176
+
/**
177
+
* @return time remaining (in seconds) to complete this objective,
178
+
* returns 0.0 if unlimited
179
+
*/
180
+
val timeRemaining: Double
181
+
get() = timeRemainingProp.value
182
+
183
+
fun timeRemainingProperty(): ReadOnlyDoubleProperty = timeRemainingProp.readOnlyProperty
184
+
142
185
protected val successProp = ReadOnlyBooleanWrapper()
143
186
144
187
private val successListener = javafx.beans.value.ChangeListener<Boolean> { _, _, isReached ->
@@ -152,12 +195,30 @@ constructor(
152
195
successProp.addListener(successListener)
153
196
}
154
197
198
+
override fun onUpdate(tpf: Double) {
199
+
if (state != QuestState.ACTIVE)
200
+
return
201
+
202
+
// ignore if no duration is set
203
+
if (expireDuration.lessThanOrEqualTo(Duration.ZERO))
204
+
return
205
+
206
+
val remaining = timeRemaining - tpf
207
+
208
+
if (remaining <= 0) {
209
+
timeRemainingProp.value = 0.0
210
+
fail()
211
+
} else {
212
+
timeRemainingProp.value = remaining
213
+
}
214
+
}
215
+
155
216
fun complete() {
156
217
if (state != QuestState.ACTIVE) {
157
218
return
158
219
}
159
220
160
-
unbind()
221
+
unbindFromVars()
161
222
successProp.value = true
162
223
}
163
224
@@ -166,7 +227,7 @@ constructor(
166
227
return
167
228
}
168
229
169
-
unbind()
230
+
unbindFromVars()
170
231
successProp.value = false
171
232
clean()
172
233
stateProp.value = QuestState.FAILED
@@ -175,19 +236,24 @@ constructor(
175
236
/**
176
237
* Transition from FAILED -> ACTIVE.
177
238
*/
178
-
fun reactivate(vars: PropertyMap) {
239
+
fun reactivate() {
179
240
if (state != QuestState.FAILED) {
180
241
return
181
242
}
182
243
183
244
stateProp.value = QuestState.ACTIVE
245
+
timeRemainingProp.value = expireDuration.toSeconds()
184
246
successProp.addListener(successListener)
185
-
bindTo(vars)
247
+
bindToVars()
186
248
}
187
249
188
-
abstract fun bindTo(vars: PropertyMap)
250
+
/**
251
+
* Bind the state to variables, so that the state
252
+
* is updated as variables change.
253
+
*/
254
+
internal abstract fun bindToVars()
189
255
190
-
internal fun unbind() {
256
+
internal fun unbindFromVars() {
191
257
successProp.unbind()
192
258
}
193
259
@@ -203,6 +269,8 @@ private class IntQuestObjective
203
269
*/
204
270
description: String,
205
271
272
+
vars: PropertyMap,
273
+
206
274
/**
207
275
* Variable name of an int property from the world properties to track.
208
276
*/
@@ -220,9 +288,9 @@ private class IntQuestObjective
220
288
*/
221
289
expireDuration: Duration = Duration.ZERO
222
290
223
-
) : QuestObjective(description, expireDuration) {
291
+
) : QuestObjective(description, vars, expireDuration) {
224
292
225
-
override fun bindTo(vars: PropertyMap) {
293
+
override fun bindToVars() {
226
294
successProp.bind(
227
295
vars.intProperty(varName).greaterThanOrEqualTo(varValue)
228
296
)
@@ -236,6 +304,8 @@ private class BooleanQuestObjective
236
304
*/
237
305
description: String,
238
306
307
+
vars: PropertyMap,
308
+
239
309
/**
240
310
* Variable name of a boolean property from the world properties to track.
241
311
*/
@@ -252,9 +322,9 @@ private class BooleanQuestObjective
252
322
*/
253
323
expireDuration: Duration = Duration.ZERO
254
324
255
-
) : QuestObjective(description, expireDuration) {
325
+
) : QuestObjective(description, vars, expireDuration) {
256
326
257
-
override fun bindTo(vars: PropertyMap) {
327
+
override fun bindToVars() {
258
328
successProp.bind(
259
329
vars.booleanProperty(varName).isEqualTo(SimpleBooleanProperty(varValue))
260
330
)
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4