aboutsummaryrefslogtreecommitdiff
path: root/src/scalevalapokalypsi/Model/Adventure.scala
blob: dd55727a39c7fee5afbcdccc427cdeceb5e05977 (plain)
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package scalevalapokalypsi.Model

import scala.collection.mutable.Map
import scala.collection.immutable
import scalevalapokalypsi.Model.Entities.*
import scalevalapokalypsi.Model.Entities.NPCs.*
import scala.math.min


// This file is different from other files in that this file needs to have lots
// of long lines of text. Thus I've chosen to take the liberty to write
// carelessly lines longer than 80 characters, which is not allowed in other
// files (obviously excluding long strings that will be printed, because
// splitting them apart in the file would make it a pain to grep for them.)

/** The class `Adventure` holds data of the game world and provides methods
  * for implementing a user interface for it.
  *
  * Data about the game world includes remote players, other entities and areas.
  * The methods are documented below.
 *
 * @param playerNames a vector of the names of players to create and add
 *                    to the adventure straight away
  */
class Adventure(val playerNames: Vector[String]):

	private val chieftainsGates = Area(
		"Kyläpäällikön portti",
		"Olet kyläpäällikön talon porttien edessä. Kylänraitti jatkuu pohjoiseen."
	)
	private val villageMain = Area(
		"Kylänraitti",
		"Olet kylänraitilla. Monet kylän asukkaista ovat liikkeellä."
	)
	private val bar = Area(
		"Baari",
		"[TODO] Olet baarissa. Tavallisesti täällä soisi musiikki ja ihmiset tanssivat ilosta, mutta nyt täällä nuokkuu vain muutama ihmistä baaritiskillä."
	)
	private val villageNorth = Area(
		"Kylän pohjoinen portti",
		"Olet kylän pohjoisen sisäänkäynnin ulkopuolella. Luukallio siintää idässä. Kylästänne vie tie naapurikylää kohti pohjoiseen. Lännessä virtaa Kattojoki, jonka anneista kylänne kalastajat saavat elantonsa. Heillä onkin ollut töitä viime aikoina, kun metsästäjät ovat olleet vastahakoisia lähtemään Luumetsään."
	)
	private val robberRoad = Area(
		"Tie naapurikylään",
		"Kuljet tietä naapurikylän suuntaan. Yhtäkkiä rosvojoukko yllättää sinut."
	)
	private val villageEast = Area(
		"Itäinen pelto",
		"Olet pellolla. Näet idässä Luukalliota ympäröivän, synkän näköisen Luumetsän. Voit vannoa näkeväsi siellä epämääräistä liikettä."
	)
	private val westForest = Area(
		"Luumetsä",
		"Olet metsässä Luukallion länsipuolella. Kuulet ympäriltäsi epämääräisiä ääniä, eivätkä ne kaikki ole eläinten aiheuttamia."
	)
	private val northForest = Area(
		"Luumetsä",
		"[TODO] Olet metsässä Luukallion pohjoispuolella. Kuulet ympäriltäsi epämääräisiä ääniä."
	)
	private val northOfNorthForest = Area(
		"Luumetsä",
		"Luumetsä jatkuu jatkumistaan..."
	)
	private val eastOfEastForest = Area(
		"Luumetsä",
		"Luumetsä jatkuu jatkumistaan..."
	)
	private val southOfSouthForest = Area(
		"Luumetsä",
		"Luumetsä jatkuu jatkumistaan..."
	)
	private val eastForest = Area(
		"Luumetsä",
		"[TODO] idk mitä täällä on?"
	)
	private val southForest = Area(
		"Luumetsä",
		"Olet Luukallion eteläpuolella. Näet kallion seinällä sileän kohdan, johon on kirjoitettu riimuja, joita et tunnista. Seinää katsomassa seisoo yksi kyläläisistä, jotka koottiin etsimään teitä piinaavan ilmiön alkulähdettä."
	)
	private val villageSouth = Area(
		"Eteläinen pelto",
		"Olet pellolla kylän eteläpuolella. Näet pellon laidalla pienen metsäaukion. Joki virtaa länsipuolellasi."
	)
	private val forestInSouthField = Area(
		"Metsäaukio",
		"Olet pienellä metsäaukiolla. Näet puiden lomassa pienen majan, josta nousee savua."
	)
	private val hutInFieldForest = Area(
		"Erakon tupa",
		"Olet pimeässä majassa, jonka ilmassa hiki ja yrtinkatku sekoittuvat."
	)
	private val caveDescent = Area(
		"Käytävä kallion uumeniin",
		"Olet hämärässä, soihduin valaistussa käytävässä. Se näyttää vievän syvälle kallion uumeniin. Katosta tippuu hiljalleen jotakin punaista nestettä."
	)
	private val secondCaveDescent = Area(
		"Käytävä kallion uumeniin",
		"Olet käytävässä, joka vie maan pinnalta syvälle kallion uumeniin. Käytävä on tässä kohtaa hieman laajempi."
	)
	private val bossDoor = Area(
		"Vuoren aula",
		"Tunnet kallion painostavan kivimassan päälläsi. Olet puolikaaren muotoisessa huoneessa. Edessäsi on avainreiällinen ovi."
	)
	private val storageRoom = Area(
		"Varastohuone",
		"Olet varastohuoneessa. Näet täällä tynnyreitä, joiden sisältö ei näytä siltä, että tahdot koskea siihen. Tavarat huoneessa näyttävät siltä, että niitä saatettaisiin käyttää joihinkin kulttimenoihin. Luulet, että yksi tynnyreistä on täynnä verta."
	)
	private val otherCaveRoom = Area(
		"Idk",
		"[TODO] mitä tänne?"
	)
	private val bossFightRoom = Area(
		"Suuri huone kallion sisässä",
		"Olet suuressa, ympyränmuotoisessa huoneessa. Lattialle on piirretty verinen kuvio, ja soihdut lepattavat punaista tulta."
	)
	private val mountaintop = Area(
		"Kalliolla kukkulalla",
		"Olet kiivennyt Luukallion huipulle. Näet täältä kotikyläsi peukun kokoisena. Takanasi levittyy Luumetsä pitkänä ja pimeänä, mutta taivaalla valkoiset pilvet halkovat horisonttia. Rinteet ovat kaikkiin suuntiin jyrkkiä paitsi länteen."
	)
	private val chieftainsGarden = Area(
		"Kyläpäällikön piha",
		"Olet kyläpäällikön pihalla. Sinun ei todellakaan pitäisi olla täällä ilman lupaa."
	)
	private val chieftainsHouse = Area(
		"Kylänpäällikön talo",
		"Olet suuressa aulassa kyläpäällikön talossa."
	)


	chieftainsGates   .setNeighbors(Vector("pohjoiseen" -> villageMain))
	villageMain       .setNeighbors(Vector("etelään"    -> chieftainsGates,    "baariin"         -> bar,                "pohjoiseen" -> villageNorth))
	bar               .setNeighbors(Vector("ulos"       -> villageMain))
	villageNorth      .setNeighbors(Vector("etelään"    -> villageMain,        "pohjoiseen"      -> robberRoad,         "itään"      -> villageEast))
	robberRoad        .setNeighbors(Vector("etelään"    -> villageNorth,       "pohjoiseen"      -> robberRoad))
	villageEast       .setNeighbors(Vector("pohjoiseen" -> villageNorth,       "etelään"         -> villageSouth,       "itään"      -> westForest))
	villageSouth      .setNeighbors(Vector("pohjoiseen" -> villageEast,        "itään"           -> villageEast,        "metsikköön" -> forestInSouthField))
	forestInSouthField.setNeighbors(Vector("mökkiin"    -> hutInFieldForest,   "pois metsiköstä" -> villageSouth))
	hutInFieldForest  .setNeighbors(Vector("ulos"       -> forestInSouthField))
	mountaintop       .setNeighbors(Vector("länteen"    -> westForest))
	northForest       .setNeighbors(Vector("pohjoiseen" -> northOfNorthForest, "itään"           -> eastForest,         "länteen"    -> westForest))
	eastForest        .setNeighbors(Vector("pohjoiseen" -> northForest,        "etelään"         -> southForest,        "itään"      -> eastOfEastForest))
	southForest       .setNeighbors(Vector("itään"      -> eastForest,         "länteen"         -> westForest,         "etelään"    -> southOfSouthForest))
	westForest        .setNeighbors(Vector("pohjoiseen" -> northForest,        "etelään"         -> southForest,        "länteen"    -> villageEast,        "itään"   -> mountaintop))
	northOfNorthForest.setNeighbors(Vector("pohjoiseen" -> northOfNorthForest, "itään"           -> northOfNorthForest, "länteen"    -> northOfNorthForest, "etelään" -> northForest))
	eastOfEastForest  .setNeighbors(Vector("pohjoiseen" -> eastOfEastForest,   "itään"           -> eastOfEastForest,   "länteen"    -> eastForest,         "etelään" -> eastOfEastForest))
	southOfSouthForest.setNeighbors(Vector("pohjoiseen" -> southForest,        "itään"           -> southOfSouthForest, "länteen"    -> southOfSouthForest, "etelään" -> southOfSouthForest))
	caveDescent       .setNeighbors(Vector("ulos"       -> southForest,        "syvemmälle"      -> secondCaveDescent))
	secondCaveDescent .setNeighbors(Vector("ylemmäs"    -> caveDescent,        "syvemmälle"      -> bossDoor))
	bossDoor          .setNeighbors(Vector("länteen"    -> storageRoom,        "itään"           -> otherCaveRoom,      "ulos"       -> secondCaveDescent))
	storageRoom       .setNeighbors(Vector("ulos"       -> bossDoor))
	otherCaveRoom     .setNeighbors(Vector("ulos"       -> bossDoor))
	chieftainsGarden  .setNeighbors(Vector("pohjoiseen" -> chieftainsGates,    "kyläpäällikön taloon" -> chieftainsHouse))
	chieftainsHouse   .setNeighbors(Vector("ulos"       -> chieftainsGarden))

	chieftainsHouse.addItem(Item("siivilä", "mystinen, musta esine, jonka pohjassa on reikiä ja jota koristaa yliviivattua silmää kuvastava logo", 1))


	val entities: Map[String, Entity] = Map()

	val players: Map[String, Player] = Map()
	playerNames.foreach(this.addPlayer(_))

	val npcs: Map[String, NPC] = Map()

	private val zombieAttrs = Vector(
		("mädäntyvä kyläläinen rääsyissä", westForest, 20),
		("mädäntyvä kyläläinen mekossa", westForest, 20),
		("räsyinen olento Joukon vaatteissa", northOfNorthForest, 10)
	)
	zombieAttrs.foreach(z =>
		val zombie = Zombie(this, z(0), z(1), z(2))
		npcs += z(0) -> zombie
		z(1).addEntity(zombie)
	)

	object JoukosDad extends NPC(this, "Joukon isä", bar, 100, 100):
		def act(): Unit = ()
		private var hasBeenTalkedTo: Boolean = false
		def getDialog: String =
			if northForest.getEntity(
				"räsyinen olento Joukon vaatteissa"
			).isDefined || !hasBeenTalkedTo then
				hasBeenTalkedTo = true
				"Voi minun poikaani! Mikä karmea kohtalo! Löytäisitkö hänet vallanneen olennon vuorilta? Päästäisitkö poikani kärsimyksestä?"
			else
				"Ai se on tehty? Voi sentään. No, nyt hän ainakin on saavuttanut levon. Kunpa näin ei olisi tarvinnut käydä."
	end JoukosDad

	object VillageChieftainGuard extends NPC(this, "Vartija", chieftainsGates, 100, 100):
		private var isAsleep = false
		def act(): Unit =
			if this.location.getItemNames.exists(_ == "oluttuoppi") then
				this.location.observeEvent(this.pickUp("oluttuoppi"))
				this.location.observeEvent(Event(immutable.Map.empty, s"${this.name} juo oluen ja sammuu."))
				this.isAsleep = true
				this.location.setNeighbor("etelään", chieftainsGarden)
		def getDialog: String =
			if !this.isAsleep then
				"Et voi mennä kyläpäällikön taloon ilman kutsua."
			else
				"ZzzzZzZZZz..."
	end VillageChieftainGuard


	object Bartender extends NPC(this, "baarin pitäjä", bar, 100, 100):

		private var dialogIndex = 0
		private val dialogs = Vector(
			"Onnea matkaan. Tarjoan sinulle tuopin olutta rohkaisuksi.",
			"Onnea matkaan."
		)

		def act(): Unit = ()

		def getDialog: String =
			if dialogIndex == 0 then
				this.location.addItem(Item(
					"oluttuoppi",
					"Tuopillinen kuohuvaa ja raikasta olutta. Se tuoksuu aika vahvalta.",
					1
				))
				this.location.observeEvent(
					Event(
						immutable.Map.empty,
						"Baarimikko kaataa tuoppiin olutta ja asettaa oluttuopin pöydälle."
					)
				)
			dialogIndex = min(dialogIndex + 1, this.dialogs.length)
			dialogs(dialogIndex - 1)
		end getDialog

	end Bartender

	//object Hermit extends NPC(this, "Tietäjäerakko", hutInFieldForest, 100, 100):
	//	
	//	private var scrollRecipients = 

//	object ShoutingCultist extends Cultist(this, "Kaapupäinen henkilö", secondCaveDescent, 100, 100):
//		private var hasShouted = false
//		override def act(): Unit =
//			val playerLocations = players.values.map(_.location)
//			if !hasShouted && playerLocations contains caveDescent then
//				caveDescent.observe(Event(immutable.Map.empty, "Kuulet huudon: “Tunkeilija! Valmistautukaa!”"))
//				hasShouted = true
//			super.act()
//	end ShoutingCultist
//	val cultist2 = Cultist(this, "Silmätön kaljupää", secondCaveDescent)
	
	this.npcs += Bartender.name -> Bartender
	this.bar.addEntity(Bartender)
	this.npcs += JoukosDad.name -> JoukosDad
	this.bar.addEntity(JoukosDad)
	this.npcs += VillageChieftainGuard.name -> VillageChieftainGuard
	this.chieftainsGates.addEntity(VillageChieftainGuard)
//	this.npcs += ShoutingCultist.name -> ShoutingCultist
//	this.secondCaveDescent.addEntity(ShoutingCultist)
//	this.npcs += cultist2
//	this.secondCaveDescent.addEntity(cultist2)


	object SeparatorScroll extends Item(
		"separoitumisen laulukäärö",
		"Laulukäärö, joka antaa sanat separoitumisen laululle, joka saa aikaan raon jopa harmaaseen kiveen.",
		1
	):

		class SeparatorEffect(actor: Entity) extends SingEffect(actor):
			def getVerses: Vector[String] =
				Vector(
					"Separoidu kova kivi",
					"jakaudu ja ratkeapi",
					"laske minut syvyyksiisi",
					"kalmankylmiin lähteisiisi"
				)

			def apply(quality: Float): Event =
				if actor.location == southForest && quality > .1 && !southForest.hasNeighbor("kallion sisään") then
					southForest.setNeighbor("kallion sisään", caveDescent)
					Event(
						Vector(actor -> "Kivi halkeaa rytinällä. Nyt näet sisään soihduilla valaistuun käytävään, joka johtaa kallion uumeniin.").toMap,
						s"Näet kun kivi halkeaa kahtia henkilön ${actor.name} laulun voimasta. Kallion sisästä paljastuu käytävä."
					)
				else if actor.location == southForest && quality <= .1 then
					Event(
						Vector(actor -> "Kivi natisee liitoksissaan, mutta jää odottamaan voimallisempaa laulua.").toMap,
						s"Kivi natisee hieman kun ${actor.name} laulaa, mutta mitään merkittävää ei tapahdu."
					)
				else
					Event(Vector(actor -> "Laulat laulun, mutta mitään ei tapahdu.").toMap, "")
		end SeparatorEffect

		override def use(user: Entity): Option[Event] =
			user match
				case p: Player =>
					p.setSingEffect(SeparatorEffect(p))
					Some(Event(
						Vector(user -> "Avaat käärön varovasti ja aloitat separoitumisen laulun.").toMap,
						s"${user.name} alkaa laulamaan kääröstä."
					))
				case other =>
					None
		end use

	end SeparatorScroll

	bar.addItem(SeparatorScroll)

	def takeNpcTurns(): Unit =
		npcs.values.foreach(_.act())

	/** Adds a player entity with the specified name to the game.
	  *
	  * @param name the name of the player entity to add
	  * @return the created player entity
	  */
	def addPlayer(name: String): Player =
		val newPlayer = Player(this, name, chieftainsGates)
		chieftainsGates.addEntity(newPlayer)
		this.entities += name -> newPlayer
		players += name -> newPlayer
		newPlayer

	/** Removes the given entity without further observations. Makes sense in the
	  * game mostly if the entity's HP is nonpositive.
	  *
	  * Removes the entity both from the adventure and the game world
	  * (i.e. the entitys area).
	  *
	  * @param name the name of the entity to remove
	  * @return whether there was an entity to remove with the given name
	  */
	def removeEntity(name: String): Boolean =
		this.players.remove(name)
		this.entities.remove(name).orElse(this.npcs.remove(name)) match
			case Some(e) =>
				e.location.removeEntity(name)
				true
			case None => false

	/** Gets the player entity with the specified name.
	 *
	 * @param name name of the player to find
	 * @return the player, if one with the name was found
	 */
	def getPlayer(name: String): Option[Player] = this.players.get(name)

	def getEntity[A >: Entity](name: String) =
		this.players.get(name)
			.orElse(this.npcs.get(name))
			.getOrElse(this.entities.get(name))

end Adventure