game.js 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  1. import * as THREE from 'three'
  2. import gsap from 'gsap'
  3. import { Howl, Howler } from 'howler'
  4. import LevelLoader from './library/levelloader.js'
  5. import { loadGltf } from './library/loadgltf.js'
  6. import { loadRgbeBackground } from './library/loadbackground.js'
  7. import { getForwardVector, getRightVector, drawDebugLine } from './library/mathhelpers.js'
  8. import Ingredient from './library/ingredient.js'
  9. import './library/arrayhelpers.js'
  10. // Shaders
  11. import firefliesVertex from './shaders/firefly/vertex.glsl'
  12. import firefliesFragment from './shaders/firefly/fragment.glsl'
  13. import cauldronVertex from './shaders/cauldron/vertex.glsl'
  14. import cauldronFragment from './shaders/cauldron/fragment.glsl'
  15. // UI
  16. import { brewTutorialPrompt, closeMainMenuUI, mainMenuUI, openMainMenuUI } from './ui/mainmenuui.js'
  17. import { brewUI, openBrewUI, closeBrewUI } from './ui/gameui.js'
  18. import { closeShopUI, openShopUI, shopTutorialPrompt, shopUI } from './ui/shopui.js'
  19. import { navigationUI, hideNavigationUI, nextRoom, previousRoom } from './ui/navigationui.js'
  20. // Game Data
  21. import * as levelData from './data/level.json'
  22. import * as ingredientInfo from './data/ingredients.json'
  23. import * as potionInfo from './data/potions.json'
  24. import Shopper from './shopper.js'
  25. import { buildCanvasText } from './library/canvastext.js'
  26. import { hideGameStatusUI, updateGameStatusUI } from './ui/gamestatusui.js'
  27. export const GAME_SAVE_KEY = "spookonomics-v1"
  28. const raycast = new THREE.Raycaster()
  29. let game, jackolantern, doorway, directionalLight, doorway2, lollipop, candle, candleLit, book
  30. const ingredients = []
  31. const soundEffects = {}
  32. const stageData = {}
  33. export const ROOM_SHOP = 1;
  34. export const ROOM_BREW = 2;
  35. export const ROOM_MARKET = 3;
  36. const shelfSlots = [
  37. { "position": new THREE.Vector3(-3.5, 1.5, -3.5), "motionPath": [{ x: -3, y: 1.3, z: -3.5 }, { x: -1.5, y: 2, z: 0 }, { x: -0.5, y: 2, z: 0 }, { x: 0, y: 0.5, z: 0 }] },
  38. { "position": new THREE.Vector3(-4.5, 1.5, -3.5), "motionPath": [{ x: -3, y: 1.3, z: -3.5 }, { x: -1.5, y: 2, z: 0 }, { x: -0.5, y: 2, z: 0 }, { x: 0, y: 0.5, z: 0 }] },
  39. { "position": new THREE.Vector3(-4.5, 0, -3.0), "motionPath": [{ x: -3, y: 1.3, z: -3.5 }, { x: -1.5, y: 2, z: 0 }, { x: -0.5, y: 2, z: 0 }, { x: 0, y: 0.5, z: 0 }] },
  40. { "position": new THREE.Vector3(-3.3, 3, -3.5), "motionPath": [{ x: -3, y: 1.3, z: -3.5 }, { x: -1.5, y: 2, z: 0 }, { x: -0.5, y: 2, z: 0 }, { x: 0, y: 0.5, z: 0 }] }
  41. ]
  42. const storeShelfSlots = [
  43. { "position": new THREE.Vector3(-12.5, 2.75, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  44. { "position": new THREE.Vector3(-11.5, 2.75, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  45. { "position": new THREE.Vector3(-8.5, 2.75, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  46. { "position": new THREE.Vector3(-7.5, 2.75, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  47. { "position": new THREE.Vector3(-8.5, 1.25, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  48. { "position": new THREE.Vector3(-7.5, 1.25, -3.25), "motionPath": [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }] },
  49. ]
  50. const customerSlots = [
  51. { "position": new THREE.Vector3(-12.9, 0.1, 3) },
  52. { "position": new THREE.Vector3(-13.1, 0.1, 4) },
  53. { "position": new THREE.Vector3(-12.7, 0.1, 5) },
  54. { "position": new THREE.Vector3(-13.1, 0.1, 6) },
  55. { "position": new THREE.Vector3(-12.9, 0.1, 7) },
  56. { "position": new THREE.Vector3(-13, 0.1, 8) },
  57. ]
  58. const bubblePositions = []
  59. const bubbleVelocity = []
  60. const bubbleLifespan = []
  61. const bubbleScale = []
  62. const bubbleCount = 15
  63. async function beginBrew(game, stageData) {
  64. if (!stageData.potionToBrew) {
  65. stageData.soundEffects['audio/witch_cackle1.ogg'].play()
  66. return
  67. }
  68. stageData.cauldron.isBrewing = true
  69. closeBrewUI(game, stageData)
  70. stageData.brewWitch.stir = true
  71. stageData.brewWitch.bounce = true
  72. stageData.spoon.visible = true
  73. stageData.selectedIngredients.forEach(ingredientName => {
  74. ingredients.find((ingredient => ingredient.getName() == ingredientName)).beginBrew()
  75. })
  76. stageData.bottle1 = await loadGltf(game, stageData.potionToBrew.model)
  77. stageData.bottle1.position.y = -1.5
  78. game.scene.add(stageData.bottle1)
  79. stageData.selectedIngredients = []
  80. stageData.soundEffects['audio/click1.ogg'].play()
  81. const nextColorData = stageData.potionToBrew.color
  82. stageData.cauldronUniforms.uNextPotionColor.value = new THREE.Color(nextColorData.r, nextColorData.g, nextColorData.b)
  83. stageData.cauldronUniforms.uBlendTime.value = 0.0
  84. setTimeout(() => {
  85. stageData.soundEffects['audio/bubbling.mp3'].volume(0.5)
  86. stageData.soundEffects['audio/bubbling.mp3'].play()
  87. stageData.bubbleParticles.visible = true
  88. gsap.to(stageData.cauldronUniforms.uBlendTime, {
  89. value: 1, duration: 4.5, onComplete: () => {
  90. stageData.cauldronUniforms.uPotionColor.value = stageData.cauldronUniforms.uNextPotionColor.value
  91. stageData.cauldronUniforms.uBlendTime.value = 0.0
  92. }
  93. })
  94. }, 900)
  95. setTimeout(() => {
  96. addValueToSave(stageData.potionInventory, 'potion-inventory', stageData.potionToBrew.name)
  97. stageData.potionToBrew = null
  98. stageData.selectedIngredients = []
  99. stageData.bottle1.position.set(0, -1.5, 0)
  100. stageData.soundEffects['audio/bubbling.mp3'].fade(1, 0, 300)
  101. stageData.brewWitch.stir = false
  102. stageData.brewWitch.bounce = false
  103. stageData.spoon.visible = false
  104. stageData.bubbleParticles.visible = false
  105. gsap.to(stageData.bottle1.position, {
  106. duration: 3, y: 1.5, ease: "elastic", onComplete: () => {
  107. setTimeout(() => {
  108. game.scene.remove(stageData.bottle1)
  109. //TODO play potion particle effect
  110. stageData.cauldron.isBrewing = false
  111. }, 1000)
  112. }
  113. })
  114. }, 5000)
  115. }
  116. async function customerBuilder(randomPotion, delay) {
  117. const shopperPosition = customerSlots[stageData.customers.length].position
  118. const randomIndex = Math.floor(Math.random() * 3)
  119. let shopper
  120. switch (randomIndex) {
  121. case 0:
  122. shopper = new Shopper(game, 'characters/assembled_character_1.gltf.glb', randomPotion, new THREE.Vector3(3.5, 3.5, 3.5), shopperPosition, Math.PI, function (elapsedTime) {
  123. this.object3d.position.y = 0.0625 * Math.sin(2 * elapsedTime) + 0.5
  124. })
  125. break;
  126. case 1:
  127. shopper = new Shopper(game, 'characters/character_skeleton_minion.gltf', randomPotion, new THREE.Vector3(1, 1, 1), shopperPosition, Math.PI, function (elapsedTime) {
  128. this.object3d.scale.x = 1 + (0.03125 * Math.sin(11.8 * elapsedTime))
  129. this.object3d.scale.y = 1 + (-0.03125 * Math.sin(11.8 * elapsedTime))
  130. this.object3d.scale.z = 1 + (0.03125 * Math.sin(11.8 * elapsedTime))
  131. })
  132. break;
  133. case 2:
  134. shopper = new Shopper(game, 'characters/ghost_1.gltf.glb', randomPotion, new THREE.Vector3(3.5, 3.5, 3.5), shopperPosition, Math.PI, function (elapsedTime) {
  135. this.object3d.position.y = 0.0625 * Math.sin(2.2 * elapsedTime) + 0.5
  136. })
  137. break;
  138. }
  139. await shopper.init(delay)
  140. return shopper
  141. }
  142. async function beginSell(game, stageData) {
  143. if (stageData.potionStocked.length < 1) {
  144. stageData.soundEffects['audio/witch_cackle1.ogg'].play()
  145. return
  146. }
  147. stageData.soundEffects['audio/click1.ogg'].play()
  148. stageData.isSellingPotions = true
  149. let randomPotions = []
  150. randomPotions.push(...stageData.potionStocked)
  151. randomPotions.push(...stageData.potionInventory)
  152. randomPotions.push(...stageData.potionInfo.map(info => info.name))
  153. randomPotions.shuffle()
  154. let numberOfCustomers = Math.min(randomPotions.length, Math.floor(Math.random() * 3) + 3)
  155. for (let i = 0; i < numberOfCustomers; i++) {
  156. const desiredPotion = randomPotions.shift()
  157. const potionInfo = stageData.potionInfo.find(info => info.name == desiredPotion)
  158. stageData.customers.push(await customerBuilder(potionInfo, i))
  159. }
  160. setTimeout(() => {
  161. stageData.soundEffects['audio/store-entrance-bell.ogg'].play()
  162. }, 500)
  163. stageData.customers[0].showDesire()
  164. closeShopUI(game, stageData)
  165. }
  166. export async function updatePotionShelfDisplay() {
  167. let storeShelfSpot = 0
  168. for (let i = 0; i < storeShelfSlots.length; i++) {
  169. if (stageData.displayedPotions[i]) {
  170. game.entities.remove(stageData.displayedPotions[i])
  171. game.scene.remove(stageData.displayedPotions[i])
  172. stageData.displayedPotions[i] = null
  173. }
  174. const currentPotionInfo = stageData.potionInfo.find(potion => stageData.potionStocked[i] == potion.name)
  175. if (!currentPotionInfo) {
  176. continue
  177. }
  178. const currentPotion = await loadGltf(game, currentPotionInfo.model)
  179. currentPotion.position.copy(storeShelfSlots[storeShelfSpot++].position)
  180. currentPotion.potionData = currentPotionInfo
  181. stageData.displayedPotions[i] = currentPotion
  182. game.entities.push(currentPotion)
  183. game.scene.add(currentPotion)
  184. }
  185. }
  186. export function addValueToSave(container, key, value) {
  187. let containerString = localStorage.getItem(`${GAME_SAVE_KEY}-${key}`)
  188. if (!containerString) {
  189. container = [value]
  190. } else {
  191. container.push(value)
  192. }
  193. localStorage.setItem(`${GAME_SAVE_KEY}-${key}`, JSON.stringify(container))
  194. }
  195. export function addAmountToSave(key, value) {
  196. let amount = parseInt(localStorage.getItem(`${GAME_SAVE_KEY}-${key}`)) ?? 0
  197. amount += value
  198. localStorage.setItem(`${GAME_SAVE_KEY}-${key}`, amount)
  199. return amount
  200. }
  201. export function removeValueFromSave(container, key, value) {
  202. let containerString = localStorage.getItem(`${GAME_SAVE_KEY}-${key}`)
  203. if (!containerString) {
  204. container = []
  205. } else {
  206. container.remove(value)
  207. }
  208. localStorage.setItem(`${GAME_SAVE_KEY}-${key}`, JSON.stringify(container))
  209. }
  210. export function clearSaveData(stageData) {
  211. localStorage.removeItem(`${GAME_SAVE_KEY}-potion-inventory`)
  212. localStorage.removeItem(`${GAME_SAVE_KEY}-potion-stocked`)
  213. localStorage.setItem(`${GAME_SAVE_KEY}-potion-inventory`, JSON.stringify([]))
  214. stageData.potionInventory = []
  215. localStorage.setItem(`${GAME_SAVE_KEY}-potion-stocked`, JSON.stringify([]))
  216. stageData.potionStocked = []
  217. stageData.currency = 100
  218. localStorage.setItem(`${GAME_SAVE_KEY}-currency`, stageData.currency)
  219. stageData.currentDay = 1
  220. localStorage.setItem(`${GAME_SAVE_KEY}-currentday`, stageData.currentDay)
  221. }
  222. export function loadSaveData(stageData) {
  223. let potionInventoryString = localStorage.getItem(`${GAME_SAVE_KEY}-potion-inventory`)
  224. if (!potionInventoryString) {
  225. localStorage.setItem(`${GAME_SAVE_KEY}-potion-inventory`, JSON.stringify([]))
  226. stageData.potionInventory = []
  227. } else {
  228. stageData.potionInventory = JSON.parse(potionInventoryString)
  229. stageData.potionInventory.sort()
  230. }
  231. let potionsStockedString = localStorage.getItem(`${GAME_SAVE_KEY}-potion-stocked`)
  232. if (!potionsStockedString) {
  233. localStorage.setItem(`${GAME_SAVE_KEY}-potion-stocked`, JSON.stringify([]))
  234. stageData.potionStocked = []
  235. } else {
  236. stageData.potionStocked = JSON.parse(potionsStockedString)
  237. stageData.potionStocked.sort()
  238. }
  239. stageData.currency = localStorage.getItem(`${GAME_SAVE_KEY}-currency`)
  240. if (!stageData.currency) {
  241. localStorage.setItem(`${GAME_SAVE_KEY}-currency`, 100)
  242. stageData.currency = 100
  243. }
  244. stageData.currentDay = localStorage.getItem(`${GAME_SAVE_KEY}-currentday`)
  245. if (!stageData.currentDay) {
  246. localStorage.setItem(`${GAME_SAVE_KEY}-currentday`, 1)
  247. stageData.currentDay = 1
  248. }
  249. }
  250. export async function init(inGame) {
  251. game = inGame
  252. loadSaveData(stageData)
  253. stageData.currentRoom = 2
  254. stageData.soundEffects = soundEffects
  255. stageData.selectedIngredients = []
  256. stageData.cameraPositions = [
  257. { "camera": new THREE.Vector3(-15, 5, 7), "focus": new THREE.Vector3(-13, 0.1, 0) },
  258. { "camera": new THREE.Vector3(1, 5, 7), "focus": new THREE.Vector3(0, 0.1, 0) },
  259. { "camera": new THREE.Vector3(12, 5, 7), "focus": new THREE.Vector3(18, 0.1, 0) },
  260. { "camera": new THREE.Vector3(1.37, 2.41, 3.77), "focus": new THREE.Vector3(-1.15, 0.79, -0.19) }
  261. ]
  262. const soundFilePaths = [
  263. 'audio/click1.ogg',
  264. 'audio/sinkWater1.ogg',
  265. 'audio/doorOpen_1.ogg',
  266. 'audio/doorClose_4.ogg',
  267. 'audio/drawKnife2.ogg',
  268. 'audio/witch_cackle1.ogg',
  269. 'audio/bubbling.mp3',
  270. 'audio/chest_close_creak.ogg',
  271. 'audio/chest_open_creak.ogg',
  272. 'audio/handleCoins.ogg',
  273. 'audio/impactGlass_medium_000.ogg',
  274. 'audio/impactGlass_medium_001.ogg',
  275. 'audio/impactGlass_medium_002.ogg',
  276. 'audio/impactGlass_medium_003.ogg',
  277. 'audio/impactGlass_medium_004.ogg',
  278. 'audio/impactWood_heavy_002.ogg',
  279. 'audio/impactWood_heavy_004.ogg',
  280. 'audio/impactSoft_medium_002.ogg',
  281. 'audio/impactSoft_medium_004.ogg',
  282. 'audio/cash-register.ogg',
  283. 'audio/store-entrance-bell.ogg'
  284. ]
  285. soundFilePaths.forEach(path => {
  286. soundEffects[path] = new Howl({
  287. src: [path],
  288. preload: true,
  289. })
  290. })
  291. soundEffects['audio/doorClose_4.ogg'].volume(0.5)
  292. soundEffects['audio/doorOpen_1.ogg'].volume(0.5)
  293. let levelLoader = new LevelLoader(game)
  294. game.camera.position.copy(stageData.cameraPositions[3].camera)
  295. game.lookAtFocus = stageData.cameraPositions[3].focus.clone()
  296. await loadRgbeBackground(game, 'kloppenheim_02_puresky_1k.hdr')
  297. game.scene.background = null
  298. game.scene.fog = new THREE.Fog(0x000000, 12, 20)
  299. let instancedMeshes = await levelLoader.load(levelData.default)
  300. instancedMeshes.forEach(instancedMesh => {
  301. game.scene.add(instancedMesh)
  302. })
  303. stageData.ingredientInfo = ingredientInfo.default
  304. stageData.potionInfo = potionInfo.default
  305. Object.values(stageData.ingredientInfo).forEach(info => {
  306. ingredients.push(new Ingredient(info, game))
  307. })
  308. ///////////////
  309. /// Brewing ///
  310. ///////////////
  311. let shelfSpot = 0
  312. ingredients.forEach(async ingredient => {
  313. await ingredient.spawn(shelfSlots[shelfSpot++])
  314. })
  315. stageData.candycorn = await loadGltf(game, 'models/candycorn.gltf')
  316. stageData.candycorn.position.x = -4.5
  317. stageData.candycorn.position.z = -3.5
  318. game.scene.add(stageData.candycorn)
  319. stageData.pumpkin2 = await loadGltf(game, 'models/pumpkin_orange.gltf')
  320. game.entities.push(stageData.pumpkin2)
  321. stageData.pumpkin2.position.x = -10
  322. stageData.pumpkin2.position.y = 1
  323. stageData.pumpkin2.position.z = 0.5
  324. game.scene.add(stageData.pumpkin2)
  325. jackolantern = await loadGltf(game, 'models/pumpkin_orange_jackolantern.gltf')
  326. game.entities.push(jackolantern)
  327. jackolantern.spin = 0
  328. jackolantern.position.set(3, 0.1, 1)
  329. jackolantern.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -Math.PI / 4)
  330. game.scene.add(jackolantern)
  331. candle = await loadGltf(game, 'models/candle_thin.gltf.glb')
  332. game.entities.push(candle)
  333. candle.position.x = 0
  334. candle.position.y = 1
  335. candle.position.z = -2.5
  336. candle.isLit = false
  337. game.scene.add(candle)
  338. candleLit = await loadGltf(game, 'models/candle_thin_lit.gltf.glb')
  339. game.entities.push(candleLit)
  340. candleLit.position.x = 0
  341. candleLit.position.y = 1
  342. candleLit.position.z = -2.5
  343. candleLit.visible = false
  344. game.scene.add(candleLit)
  345. stageData.cauldronUniforms = {
  346. uTime: { value: 0 },
  347. uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
  348. uBlendTime: { value: 0 },
  349. uPotionColor: { value: new THREE.Vector3(0, 0.8, 0.2) },
  350. uNextPotionColor: { value: new THREE.Vector3(0, 0.8, 0.2) },
  351. uTransparency: { value: 0.8 }
  352. }
  353. stageData.cauldron = await loadGltf(game, 'models/simple_cauldron.gltf.glb')
  354. const cauldronBell = stageData.cauldron.getObjectByName("Sphere015")
  355. cauldronBell.material.side = THREE.DoubleSide
  356. const cauldronTop = stageData.cauldron.getObjectByName("Sphere015_1")
  357. const cauldronTopMaterial = new THREE.ShaderMaterial({
  358. fragmentShader: cauldronFragment,
  359. vertexShader: cauldronVertex,
  360. uniforms: stageData.cauldronUniforms,
  361. transparent: true,
  362. depthWrite: false,
  363. side: THREE.DoubleSide,
  364. })
  365. cauldronTop.material = cauldronTopMaterial
  366. game.entities.push(stageData.cauldron)
  367. stageData.cauldron.position.y = 0.1
  368. game.scene.add(stageData.cauldron)
  369. stageData.brewTutorial1 = buildCanvasText("Click to Brew", { font: "86px Alice" })
  370. stageData.brewTutorial1.position.z = 2.4
  371. stageData.brewTutorial1.position.y = 0.2
  372. stageData.brewTutorial1.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI / 2)
  373. stageData.brewTutorial1.material.opacity = 0
  374. stageData.brewTutorial1.castShadow = false
  375. game.scene.add(stageData.brewTutorial1)
  376. stageData.brewWitch = await loadGltf(game, 'characters/character_witch.gltf')
  377. game.entities.push(stageData.brewWitch)
  378. stageData.brewWitch.position.x = 1
  379. stageData.brewWitch.position.y = 0.1
  380. stageData.brewWitch.position.z = -1
  381. stageData.brewWitch.stir = false
  382. stageData.brewWitch.bounce = false
  383. stageData.brewWitch.lookAt(stageData.cauldron.position)
  384. game.scene.add(stageData.brewWitch)
  385. stageData.shopWitch = await loadGltf(game, 'characters/character_witch.gltf')
  386. game.entities.push(stageData.shopWitch)
  387. stageData.shopWitch.position.x = -13
  388. stageData.shopWitch.position.y = 0.1
  389. stageData.shopWitch.position.z = -2
  390. game.scene.add(stageData.shopWitch)
  391. stageData.marketWitch = await loadGltf(game, 'characters/character_witch.gltf')
  392. game.entities.push(stageData.marketWitch)
  393. stageData.marketWitch.position.x = 20
  394. stageData.marketWitch.position.y = 0.1
  395. stageData.marketWitch.position.z = 2.5
  396. stageData.marketWitch.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI)
  397. game.scene.add(stageData.marketWitch)
  398. stageData.spoon = await loadGltf(game, 'models/spoon.gltf')
  399. game.entities.push(stageData.spoon)
  400. stageData.spoon.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI)
  401. stageData.spoon.position.y = 1.25
  402. stageData.spoon.visible = false
  403. game.scene.add(stageData.spoon)
  404. book = await loadGltf(game, 'models/book_grey.gltf.glb')
  405. game.entities.push(book)
  406. book.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI)
  407. book.position.x = 4.6
  408. book.position.y = 1.9
  409. book.position.z = -3.9
  410. game.scene.add(book)
  411. const bubbleGeometry = new THREE.SphereGeometry(0.1, 24, 24)
  412. const bubbleMaterial = cauldronTopMaterial
  413. //bubbleMaterial.uniforms.uTransparency.value = 0.3
  414. stageData.bubbleParticles = new THREE.InstancedMesh(bubbleGeometry, bubbleMaterial, bubbleCount)
  415. stageData.bubbleParticles.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
  416. stageData.bubbleParticles.visible = false
  417. game.scene.add(stageData.bubbleParticles)
  418. const matrix = new THREE.Matrix4()
  419. for (let index = 0; index < bubbleCount; index++) {
  420. bubblePositions[index] = new THREE.Vector3(0.5 * (Math.random() - 0.5), 0.5, 0.5 * (Math.random() - 0.5))
  421. bubbleLifespan[index] = 7 - (index * 0.5)
  422. bubbleScale[index] = 0.2
  423. bubbleVelocity[index] = new THREE.Vector3(0, 0.01, 0)
  424. }
  425. const quaternion = new THREE.Quaternion()
  426. const scaleVector = new THREE.Vector3(1, 1, 1)
  427. for (let index = 0; index < bubbleCount; index++) {
  428. quaternion.setFromAxisAngle(THREE.Object3D.DEFAULT_UP, 0)
  429. const scale = scaleVector.set(bubbleScale[index], bubbleScale[index], bubbleScale[index])
  430. matrix.compose(bubblePositions[index], quaternion, scale)
  431. stageData.bubbleParticles.setMatrixAt(index, matrix)
  432. }
  433. doorway = await loadGltf(game, 'models/wall_doorway.glb')
  434. doorway.position.set(-6, 0, -2)
  435. doorway.castShadow = false
  436. doorway.traverse((child) => {
  437. if (child.isMesh) {
  438. child.castShadow = false
  439. }
  440. })
  441. doorway.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI / 2)
  442. doorway.isOpen = false
  443. doorway.isMoving = false
  444. game.entities.push(doorway)
  445. game.scene.add(doorway)
  446. doorway2 = await loadGltf(game, 'models/wall_doorway.glb')
  447. doorway2.position.set(6, 0, 2)
  448. doorway2.castShadow = false
  449. doorway2.traverse((child) => {
  450. if (child.isMesh) {
  451. child.castShadow = false
  452. }
  453. })
  454. doorway2.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI / 2)
  455. doorway2.isOpen = false
  456. doorway2.isMoving = false
  457. game.entities.push(doorway2)
  458. game.scene.add(doorway2)
  459. ////////////
  460. /// Shop ///
  461. ////////////
  462. // const debugSphereGeometry = new THREE.SphereGeometry(0.1)
  463. // const debugMaterial = new THREE.MeshBasicMaterial({color: "magenta"})
  464. // const debugSphere = new THREE.Mesh(debugSphereGeometry, debugMaterial)
  465. // customerSlots.forEach(slot => {
  466. // debugSphere.position.copy(slot.position)
  467. // game.scene.add(debugSphere.clone())
  468. // })
  469. stageData.displayedPotions = []
  470. updatePotionShelfDisplay()
  471. lollipop = await loadGltf(game, 'models/lollipop_green.gltf')
  472. lollipop.position.x = -11.5
  473. lollipop.position.y = 1.28
  474. lollipop.position.z = -3.4
  475. lollipop.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2)
  476. lollipop.rotateOnAxis(new THREE.Vector3(0, 0, 1), Math.PI / 2 + -0.3)
  477. game.entities.push(lollipop)
  478. game.scene.add(lollipop)
  479. stageData.chest = await loadGltf(game, 'models/chest_large.glb')
  480. stageData.chest.scale.set(0.5, 0.5, 0.5)
  481. stageData.chest.position.x = -15.5
  482. stageData.chest.position.y = 1
  483. stageData.chest.position.z = 0
  484. stageData.chest.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI / 4)
  485. game.entities.push(stageData.chest)
  486. game.scene.add(stageData.chest)
  487. stageData.shopTutorial1 = buildCanvasText("Click to Sell", { font: "100px Alice" })
  488. stageData.shopTutorial1.position.x = -15.5
  489. stageData.shopTutorial1.position.z = 0.2
  490. stageData.shopTutorial1.position.y = 1.8
  491. stageData.shopTutorial1.scale.set(0.6, 0.6, 0.6)
  492. stageData.shopTutorial1.material.opacity = 0
  493. stageData.shopTutorial1.castShadow = false
  494. game.scene.add(stageData.shopTutorial1)
  495. stageData.coin = await loadGltf(game, 'models/coin.gltf.glb')
  496. stageData.coin.position.x = -12
  497. stageData.coin.position.y = 1.1
  498. stageData.coin.position.z = 0
  499. game.entities.push(stageData.coin)
  500. game.scene.add(stageData.coin)
  501. stageData.customers = []
  502. //////////////
  503. /// Market ///
  504. //////////////
  505. stageData.sellingSkeleton = await loadGltf(game, 'characters/character_skeleton_mage.gltf')
  506. stageData.sellingSkeleton.position.x = 20
  507. stageData.sellingSkeleton.position.y = 0.1
  508. stageData.sellingSkeleton.position.z = -1
  509. stageData.sellingSkeleton.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -Math.PI / 4)
  510. game.entities.push(stageData.sellingSkeleton)
  511. game.scene.add(stageData.sellingSkeleton)
  512. stageData.sign = await loadGltf(game, 'models/sign_left.gltf')
  513. stageData.sign.position.x = 14
  514. stageData.sign.position.y = 0.1
  515. stageData.sign.position.z = 2
  516. game.entities.push(stageData.sign)
  517. game.scene.add(stageData.sign)
  518. const mushroomCrate = await loadGltf(game, 'models/crate_mushrooms.gltf')
  519. mushroomCrate.scale.set(0.5, 0.5, 0.5)
  520. mushroomCrate.position.x = 19.5
  521. mushroomCrate.position.z = 0.5
  522. mushroomCrate.rotateOnAxis(THREE.Object3D.DEFAULT_UP, Math.PI / 4)
  523. game.entities.push(mushroomCrate)
  524. game.scene.add(mushroomCrate)
  525. const tomatoCrate = await loadGltf(game, 'models/crate_tomatoes.gltf')
  526. tomatoCrate.scale.set(0.5, 0.5, 0.5)
  527. tomatoCrate.position.x = 21
  528. game.entities.push(tomatoCrate)
  529. game.scene.add(tomatoCrate)
  530. const lettuceCrate = await loadGltf(game, 'models/crate_lettuce.gltf')
  531. lettuceCrate.scale.set(0.5, 0.5, 0.5)
  532. lettuceCrate.position.x = 18.5
  533. lettuceCrate.position.z = -0.5
  534. game.entities.push(lettuceCrate)
  535. game.scene.add(lettuceCrate)
  536. const coffin = await loadGltf(game, 'models/coffin_decorated.gltf')
  537. coffin.position.x = 23
  538. coffin.position.z = 6
  539. coffin.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -Math.PI / 4)
  540. game.scene.add(coffin)
  541. const candy = await loadGltf(game, 'models/candy_orange_A.gltf')
  542. candy.position.x = 21
  543. candy.position.y = 0.25
  544. candy.position.z = 6
  545. candy.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -Math.PI / 4)
  546. game.scene.add(candy)
  547. const candyBucket = await loadGltf(game, 'models/candy_bucket_B_decorated.gltf')
  548. candyBucket.position.x = 21.5
  549. candyBucket.position.y = 0.1
  550. candyBucket.position.z = 5.5
  551. candyBucket.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -Math.PI / 2)
  552. game.scene.add(candyBucket)
  553. /**
  554. * Fireflies
  555. */
  556. stageData.firefliesUniforms = {
  557. uTime: { value: 0 },
  558. uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) }
  559. }
  560. const firefliesMaterial = new THREE.ShaderMaterial({
  561. fragmentShader: firefliesFragment,
  562. vertexShader: firefliesVertex,
  563. uniforms: stageData.firefliesUniforms,
  564. transparent: true,
  565. depthWrite: false,
  566. blending: THREE.AdditiveBlending
  567. })
  568. const firefliesGeometry = new THREE.BufferGeometry()
  569. const firefliesCount = 50
  570. const positionArray = new Float32Array(firefliesCount * 3)
  571. const scaleArray = new Float32Array(firefliesCount)
  572. for (let i = 0; i < firefliesCount; i++) {
  573. positionArray[i * 3 + 0] = 24 * (Math.random() - 0.5) + 18
  574. positionArray[i * 3 + 1] = 4 * Math.random() + 2
  575. positionArray[i * 3 + 2] = 16 * (Math.random() - 0.5)
  576. scaleArray[i] = 0.8 * Math.random() + 1.2
  577. }
  578. firefliesGeometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3))
  579. firefliesGeometry.setAttribute('aScale', new THREE.BufferAttribute(scaleArray, 1))
  580. const fireflies = new THREE.Points(firefliesGeometry, firefliesMaterial)
  581. game.scene.add(fireflies)
  582. ////////////////
  583. /// Lighting ///
  584. ////////////////
  585. const ambientLight = new THREE.AmbientLight(0xFFFFFF, 1)
  586. game.scene.add(ambientLight)
  587. const shadowSize = 24
  588. directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.9)
  589. directionalLight.position.set(8, 10, 6)
  590. directionalLight.castShadow = true
  591. directionalLight.shadow.camera.left = -shadowSize
  592. directionalLight.shadow.camera.right = shadowSize
  593. directionalLight.shadow.camera.top = -shadowSize
  594. directionalLight.shadow.camera.bottom = shadowSize
  595. directionalLight.shadow.camera.far = 28
  596. directionalLight.shadow.mapSize.width = Math.min(game.renderer.capabilities.maxTextureSize, 2048)
  597. directionalLight.shadow.mapSize.height = Math.min(game.renderer.capabilities.maxTextureSize, 2048)
  598. directionalLight.shadow.bias = -0.005
  599. directionalLight.shadow.radius = 6
  600. directionalLight.cameraOffset = new THREE.Vector3()
  601. directionalLight.cameraOffset.copy(directionalLight.position)
  602. directionalLight.cameraOffset.sub(new THREE.Vector3(0, 0, 0))
  603. directionalLight.target = game.camera
  604. directionalLight.update = function () {
  605. const currentOffset = new THREE.Vector3()
  606. currentOffset.copy(directionalLight.cameraOffset).add(game.camera.position)
  607. directionalLight.position.set(currentOffset.x, currentOffset.y, currentOffset.z)
  608. if (directionalLight.shadow) {
  609. directionalLight.shadow.camera.position.set(currentOffset.x, currentOffset.y, currentOffset.z)
  610. }
  611. }
  612. game.scene.add(directionalLight)
  613. // const shadowHelper = new THREE.CameraHelper( directionalLight.shadow.camera )
  614. // game.scene.add(shadowHelper)
  615. mainMenuUI(game, stageData)
  616. navigationUI(game, stageData)
  617. brewUI(game, stageData)
  618. shopUI(game, stageData)
  619. updateGameStatusUI(game, stageData)
  620. stageData.beginBrew = () => { beginBrew(game, stageData) }
  621. stageData.beginSell = () => { beginSell(game, stageData) }
  622. ///////////
  623. // DEBUG //
  624. ///////////
  625. // moveToRoom(ROOM_SHOP)
  626. // closeMainMenuUI(game, stageData)
  627. // const planeGeometry = new THREE.PlaneGeometry(1,1)
  628. // const debugPlane = new THREE.Mesh(planeGeometry, textBubbleMaterial)
  629. // debugPlane.position.x = 2
  630. // debugPlane.position.y = 1
  631. // debugPlane.scale.set(2,2,2)
  632. // game.scene.add(debugPlane)
  633. }
  634. export function update(game) {
  635. let elapsedTime = game.clock.getElapsedTime()
  636. ingredients.forEach(ingredient => {
  637. ingredient.wobble()
  638. })
  639. stageData.candycorn.position.y = 0.125 * Math.sin(4.1 * elapsedTime) + 3.0
  640. stageData.candycorn.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -0.02)
  641. directionalLight.update()
  642. raycast.setFromCamera(game.mousePosition, game.camera)
  643. Howler.pos(game.camera.position.x, game.camera.position.y, game.camera.position.z)
  644. Howler.orientation(game.camera.position.x, game.camera.position.y, game.camera.position.z, 0, 1, 0)
  645. const quaternion = new THREE.Quaternion()
  646. const scaleVector = new THREE.Vector3(1, 1, 1)
  647. const matrix = new THREE.Matrix4()
  648. for (let index = 0; index < bubbleCount; index++) {
  649. bubblePositions[index].add(bubbleVelocity[index])
  650. bubbleScale[index] = Math.min(bubbleScale[index] + 0.01, 1)
  651. bubbleLifespan[index] -= 0.1
  652. if (bubbleLifespan[index] <= 0) {
  653. bubbleLifespan[index] = 7
  654. bubblePositions[index].set(0.5 * (Math.random() - 0.5), 0.5, 0.5 * (Math.random() - 0.5))
  655. bubbleScale[index] = 0.2
  656. }
  657. //quaternion.setFromAxisAngle(THREE.Object3D.DEFAULT_UP, 0)
  658. const scale = scaleVector.set(bubbleScale[index], bubbleScale[index], bubbleScale[index])
  659. matrix.compose(bubblePositions[index], quaternion, scale)
  660. stageData.bubbleParticles.setMatrixAt(index, matrix)
  661. }
  662. stageData.bubbleParticles.instanceMatrix.needsUpdate = true
  663. if (stageData.brewWitch.stir) {
  664. stageData.spoon.position.x = 0.125 * Math.sin(4 * elapsedTime)
  665. stageData.spoon.position.z = 0.125 * Math.cos(4 * elapsedTime)
  666. }
  667. if (stageData.brewWitch.bounce) {
  668. stageData.brewWitch.scale.x = 1 + (0.03125 * Math.sin(12 * elapsedTime))
  669. stageData.brewWitch.scale.y = 1 + (-0.03125 * Math.sin(12 * elapsedTime))
  670. stageData.brewWitch.scale.z = 1 + (0.03125 * Math.sin(12 * elapsedTime))
  671. } else {
  672. stageData.brewWitch.scale.x = 1
  673. stageData.brewWitch.scale.y = 1
  674. stageData.brewWitch.scale.z = 1
  675. }
  676. if (jackolantern.spin > 0) {
  677. jackolantern.spin -= 0.05
  678. jackolantern.rotateOnAxis(THREE.Object3D.DEFAULT_UP, -0.1)
  679. if (jackolantern.spin < 0) {
  680. jackolantern.spin = 0
  681. }
  682. }
  683. stageData.sellingSkeleton.scale.x = 1 + (0.03125 * Math.sin(12 * elapsedTime))
  684. stageData.sellingSkeleton.scale.y = 1 + (-0.03125 * Math.sin(12 * elapsedTime))
  685. stageData.sellingSkeleton.scale.z = 1 + (0.03125 * Math.sin(12 * elapsedTime))
  686. stageData.firefliesUniforms.uTime.value = elapsedTime
  687. stageData.cauldronUniforms.uTime.value = elapsedTime
  688. stageData.customers.forEach(customer => {
  689. customer.update(elapsedTime)
  690. })
  691. candle.position.y = 0.625 * Math.sin(0.5 * elapsedTime) + 1.3
  692. candleLit.position.y = 0.625 * Math.sin(0.5 * elapsedTime) + 1.3
  693. let intersects = raycast.intersectObjects(game.entities.filter(entity => entity.visible))
  694. if (intersects.length > 0) {
  695. document.body.style.cursor = "pointer"
  696. } else {
  697. document.body.style.cursor = "default"
  698. }
  699. }
  700. function isAChildOf(parent, childToCheck) {
  701. if (parent == childToCheck) return true
  702. if (childToCheck.parent != null) {
  703. return isAChildOf(parent, childToCheck.parent)
  704. }
  705. return false
  706. }
  707. export function onClick() {
  708. //drawDebugLine(game, witch.position, getForwardVector(witch).multiplyScalar(10), 0xff0000, 1000)
  709. //drawDebugLine(game, witch.position, getRightVector(witch).multiplyScalar(10), 0x00ff00, 1000)
  710. let intersects = raycast.intersectObjects(game.entities.filter(entity => entity.visible))
  711. if (intersects.length > 0) {
  712. intersects.every(intersect => {
  713. switch (stageData.currentRoom) {
  714. case ROOM_SHOP:
  715. //SHOP CLICK ACTIONS HERE
  716. if (isAChildOf(stageData.chest, intersect.object)) {
  717. if (stageData.isSellingPotions) {
  718. return false
  719. }
  720. if (stageData.shopInactivityHandle) {
  721. clearTimeout(stageData.shopInactivityHandle)
  722. stageData.shopInactivityHandle = 0
  723. }
  724. if (stageData.chest.isStocking) {
  725. closeShopUI(game, stageData)
  726. } else {
  727. openShopUI(game, stageData)
  728. if (stageData.shopTutorial1.material.opacity == 1) {
  729. gsap.to(stageData.shopTutorial1.material, {
  730. duration: 1.5, opacity: 0, onComplete: () => {
  731. stageData.shopTutorial1.castShadow = false
  732. }
  733. })
  734. }
  735. }
  736. return false
  737. }
  738. if (isAChildOf(stageData.shopWitch, intersect.object)) {
  739. if (stageData.isSellingPotions) {
  740. return false
  741. }
  742. if (stageData.shopInactivityHandle) {
  743. clearTimeout(stageData.shopInactivityHandle)
  744. stageData.shopInactivityHandle = 0
  745. }
  746. if (stageData.chest.isStocking) {
  747. closeShopUI(game, stageData)
  748. } else {
  749. openShopUI(game, stageData)
  750. if (stageData.shopTutorial1.material.opacity == 1) {
  751. gsap.to(stageData.shopTutorial1.material, {
  752. duration: 1.5, opacity: 0, onComplete: () => {
  753. stageData.shopTutorial1.castShadow = false
  754. }
  755. })
  756. }
  757. }
  758. return false
  759. }
  760. if (isAChildOf(stageData.coin, intersect.object)) {
  761. let coinMotionPath = [{ x: -12, y: 1.1, z: 1 }, { x: -12, y: 2, z: 2 }, { x: -15.5, y: 2, z: 2 }, { x: -15.5, y: 1.2, z: 0 }]
  762. gsap.to(stageData.coin.position, {
  763. duration: 3.5, motionPath: coinMotionPath, onComplete: () => {
  764. stageData.soundEffects['audio/handleCoins.ogg'].play()
  765. closeChest(stageData.chest, () => {
  766. stageData.coin.scale.set(0, 0, 0)
  767. stageData.coin.position.set(-12, 1.1, 0)
  768. gsap.to(stageData.coin.scale, {
  769. ease: "elastic",
  770. duration: 0.7, x: 1, y: 1, z: 1,
  771. })
  772. })
  773. }
  774. })
  775. setTimeout(() => {
  776. openChest(stageData.chest)
  777. }, 700)
  778. return false
  779. }
  780. stageData.displayedPotions.forEach((potion) => {
  781. if (isAChildOf(potion, intersect.object)) {
  782. if (stageData.isSellingPotions) {
  783. if (stageData.customers[0].isMatchingPotion(potion.potionData)) {
  784. const firstCustomer = stageData.customers[0]
  785. firstCustomer.hideDesire()
  786. const potionMotionPath = [{ x: -11.5, y: 2.75, z: -3.25 }, { x: -12, y: 2, z: -2 }, { x: -13, y: 0.75, z: 2.5 }]
  787. gsap.to(potion.position, {
  788. duration: 2, motionPath: potionMotionPath, onComplete: () => {
  789. removeValueFromSave(stageData.potionStocked, 'potion-stocked', potion.potionData.name)
  790. updatePotionShelfDisplay()
  791. firstCustomer.acceptPotion()
  792. stageData.soundEffects['audio/cash-register.ogg'].play()
  793. stageData.currency = addAmountToSave("currency", potion.potionData.value)
  794. updateGameStatusUI(game, stageData)
  795. //move money from customer to chest
  796. //add money to save data
  797. //move customer out of room
  798. nextCustomer()
  799. //display next customer desire
  800. //if no more customers or no more valid potions, end selling
  801. }
  802. })
  803. } else {
  804. //TODO: potion does not match
  805. playBottleClink()
  806. gsap.fromTo(potion.rotation, { duration: 0.1, z: 0.2 }, { duration: 0.1, z: 0 })
  807. stageData.customers[0].rejectPotion()
  808. if (stageData.customers[0].rejectCount >= 3) {
  809. stageData.customers[0].showSad()
  810. nextCustomer()
  811. }
  812. }
  813. } else {
  814. playBottleClink()
  815. gsap.fromTo(potion.rotation, { duration: 0.1, z: 0.2 }, { duration: 0.1, z: 0 })
  816. }
  817. }
  818. })
  819. if (isAChildOf(stageData.pumpkin2, intersect.object)) {
  820. if (stageData.pumpkin2.isMoving) {
  821. return
  822. }
  823. stageData.pumpkin2.isMoving = true
  824. gsap.to(stageData.pumpkin2.position, {
  825. duration: 2, y: 3, onComplete: () => {
  826. setTimeout(() => {
  827. stageData.soundEffects['audio/impactWood_heavy_002.ogg'].play()
  828. }, 400)
  829. setTimeout(() => {
  830. stageData.soundEffects['audio/impactSoft_medium_002.ogg'].play()
  831. }, 800)
  832. gsap.to(stageData.pumpkin2.position, {
  833. duration: 1, y: 1, ease: "bounce", onComplete: () => {
  834. stageData.pumpkin2.isMoving = false
  835. }
  836. })
  837. }
  838. })
  839. return false
  840. }
  841. break;
  842. case ROOM_BREW:
  843. //BREW CLICK ACTIONS HERE
  844. if (isAChildOf(jackolantern, intersect.object)) {
  845. soundEffects['audio/drawKnife2.ogg'].play()
  846. jackolantern.spin += Math.PI
  847. return false
  848. }
  849. if (isAChildOf(book, intersect.object)) {
  850. //soundEffects['audio/drawKnife2.ogg'].play()
  851. //jackolantern.spin += Math.PI
  852. let bookMotionPath = [{ x: 4.6, y: 1.9, z: 0 }, { x: 4.6, y: 1.9, z: -3.9 }]
  853. gsap.to(book.position, {
  854. duration: 6, motionPath: bookMotionPath, onComplete: () => {
  855. }
  856. })
  857. return false
  858. }
  859. if (isAChildOf(stageData.brewWitch, intersect.object)) {
  860. if (stageData.cauldron.isBrewing) {
  861. return false
  862. }
  863. if (stageData.brewInactivityHandle) {
  864. clearTimeout(stageData.brewInactivityHandle)
  865. stageData.brewInactivityHandle = 0
  866. }
  867. if (stageData.cauldron.brewMenuOpen) {
  868. closeBrewUI(game, stageData)
  869. } else {
  870. openBrewUI(game, stageData)
  871. //cauldron.isBrewing = true
  872. if (stageData.brewTutorial1.material.opacity == 1) {
  873. gsap.to(stageData.brewTutorial1.material, {
  874. duration: 1.5, opacity: 0, onComplete: () => {
  875. stageData.brewTutorial1.castShadow = false
  876. }
  877. })
  878. }
  879. }
  880. return false
  881. }
  882. if (isAChildOf(stageData.cauldron, intersect.object)) {
  883. if (stageData.cauldron.isBrewing) {
  884. return false
  885. }
  886. if (stageData.brewInactivityHandle) {
  887. clearTimeout(stageData.brewInactivityHandle)
  888. stageData.brewInactivityHandle = 0
  889. }
  890. if (stageData.cauldron.brewMenuOpen) {
  891. closeBrewUI(game, stageData)
  892. } else {
  893. openBrewUI(game, stageData)
  894. if (stageData.brewTutorial1.material.opacity == 1) {
  895. gsap.to(stageData.brewTutorial1.material, {
  896. duration: 1.5, opacity: 0, onComplete: () => {
  897. stageData.brewTutorial1.castShadow = false
  898. }
  899. })
  900. }
  901. //cauldron.isBrewing = true
  902. }
  903. // let soundId = soundEffects['audio/sinkWater1.ogg'].play()
  904. // soundEffects['audio/sinkWater1.ogg'].once('play', () => {
  905. // soundEffects['audio/sinkWater1.ogg'].volume(0.5, soundId)
  906. // // soundEffects['audio/sinkWater1.ogg'].pos(cauldron.position.x, cauldron.position.y, cauldron.position.z, soundId)
  907. // // soundEffects['audio/sinkWater1.ogg'].pannerAttr({
  908. // // panningModel: 'HRTF',
  909. // // refDistance: 1.0,
  910. // // rolloffFactor: 0.8,
  911. // // distanceModel: 'exponential',
  912. // // }, soundId)
  913. // soundEffects['audio/sinkWater1.ogg'].stereo(1, soundId)
  914. // //console.log(soundEffects['audio/sinkWater1.ogg'])
  915. // }, soundId)
  916. return false
  917. }
  918. if (isAChildOf(candle, intersect.object)) {
  919. candle.isLit = true
  920. candleLit.visible = true
  921. candle.visible = false
  922. return false
  923. }
  924. if (isAChildOf(candleLit, intersect.object)) {
  925. candle.isLit = false
  926. candleLit.visible = false
  927. candle.visible = true
  928. return false
  929. }
  930. break;
  931. case ROOM_MARKET:
  932. //MARKET CLICK ACTIONS HERE
  933. if (isAChildOf(stageData.sign, intersect.object)) {
  934. previousRoom(game, stageData)
  935. }
  936. break;
  937. }
  938. if (isAChildOf(doorway, intersect.object)) {
  939. if (stageData.currentRoom == ROOM_BREW) {
  940. previousRoom(game, stageData)
  941. } else if (stageData.currentRoom == ROOM_SHOP) {
  942. nextRoom(game, stageData)
  943. }
  944. return false
  945. }
  946. if (isAChildOf(doorway2, intersect.object)) {
  947. if (stageData.currentRoom == ROOM_BREW) {
  948. nextRoom(game, stageData)
  949. } else if (stageData.currentRoom == ROOM_MARKET) {
  950. previousRoom(game, stageData)
  951. }
  952. return false
  953. }
  954. })
  955. }
  956. }
  957. function nextCustomer() {
  958. const firstCustomer = stageData.customers.shift()
  959. if (!firstCustomer) {
  960. return
  961. }
  962. const customerMotionPath = [{ x: -11, y: 0.1, z: 3 }, { x: -8, y: 0.1, z: 3 }, { x: -4, y: 0.1, z: 10 }]
  963. setTimeout(() => {
  964. stageData.soundEffects['audio/store-entrance-bell.ogg'].play()
  965. }, 4000)
  966. gsap.to(firstCustomer.object3d.position, {
  967. duration: 8, motionPath: customerMotionPath, onComplete: () => {
  968. game.scene.remove(firstCustomer.object3d)
  969. }
  970. })
  971. gsap.to(firstCustomer.object3d.rotation, {
  972. duration: 1, y: Math.PI, onComplete: () => {
  973. }, onUpdate: () => {
  974. firstCustomer.requestBillboard.lookAt(game.camera.position)
  975. }
  976. })
  977. //const shopperPosition = customerSlots[stageData.customers.length].position
  978. //move other customers forward
  979. for (let i = 0; i < stageData.customers.length; i++) {
  980. const customer = stageData.customers[i]
  981. const shopperPosition = customerSlots[i].position
  982. gsap.to(customer.object3d.position, { duration: 1.5, x: shopperPosition.x, y: shopperPosition.y, z: shopperPosition.z })
  983. }
  984. if (stageData.customers.length == 0) {
  985. stageData.isSellingPotions = false
  986. stageData.currentDay = addAmountToSave("currentday", 1)
  987. updateGameStatusUI(game, stageData)
  988. } else {
  989. stageData.customers[0].showDesire()
  990. if (stageData.potionStocked.length < 1) {
  991. stageData.customers[0].rejectPotion(3)
  992. stageData.customers[0].showSad()
  993. nextCustomer()
  994. }
  995. }
  996. }
  997. export function playBottleClink() {
  998. setTimeout(() => {
  999. let randomSound2 = Math.floor(5 * Math.random())
  1000. stageData.soundEffects[`audio/impactGlass_medium_00${randomSound2}.ogg`].play()
  1001. }, 50)
  1002. let randomSound = Math.floor(5 * Math.random())
  1003. stageData.soundEffects[`audio/impactGlass_medium_00${randomSound}.ogg`].play()
  1004. }
  1005. function openChest(chest) {
  1006. let lid = chest.getObjectByName("chest_large_lid")
  1007. if (chest.isOpen) {
  1008. return
  1009. }
  1010. if (!chest.isMoving) {
  1011. soundEffects['audio/chest_open_creak.ogg'].play()
  1012. chest.isMoving = true
  1013. gsap.to(lid.rotation, {
  1014. duration: 2.5, x: -(Math.PI / 2) + (Math.PI / 8), ease: "elastic", onComplete: () => {
  1015. chest.isOpen = true
  1016. chest.isMoving = false
  1017. }
  1018. })
  1019. }
  1020. }
  1021. function closeChest(chest, onComplete = () => { }) {
  1022. let lid = chest.getObjectByName("chest_large_lid")
  1023. if (!chest.isOpen) {
  1024. return
  1025. }
  1026. if (!chest.isMoving) {
  1027. soundEffects['audio/chest_close_creak.ogg'].play()
  1028. chest.isMoving = true
  1029. gsap.to(lid.rotation, {
  1030. duration: 1.5, x: 0, ease: "bounce", onComplete: () => {
  1031. chest.isOpen = false
  1032. chest.isMoving = false
  1033. onComplete()
  1034. }
  1035. })
  1036. }
  1037. }
  1038. function openDoorway(doorway) {
  1039. let door = doorway.getObjectByName("wall_doorway_door")
  1040. if (doorway.isOpen) {
  1041. return
  1042. }
  1043. if (!doorway.isMoving) {
  1044. soundEffects['audio/doorOpen_1.ogg'].play()
  1045. doorway.isMoving = true
  1046. gsap.to(door.rotation, {
  1047. duration: 2.5, y: -Math.PI / 2, ease: "elastic", onComplete: () => {
  1048. doorway.isOpen = true
  1049. doorway.isMoving = false
  1050. }
  1051. })
  1052. }
  1053. }
  1054. function closeDoorway(doorway) {
  1055. let door = doorway.getObjectByName("wall_doorway_door")
  1056. if (!doorway.isOpen) {
  1057. return
  1058. }
  1059. if (!doorway.isMoving) {
  1060. soundEffects['audio/doorClose_4.ogg'].play()
  1061. doorway.isMoving = true
  1062. gsap.to(door.rotation, {
  1063. duration: 2.5, y: 0, ease: "elastic", onComplete: () => {
  1064. doorway.isOpen = false
  1065. doorway.isMoving = false
  1066. }
  1067. })
  1068. }
  1069. }
  1070. function moveToShop() {
  1071. closeBrewUI(game, stageData)
  1072. openDoorway(doorway)
  1073. const camPosition = stageData.cameraPositions[0]
  1074. gsap.to(game.camera.position, {
  1075. duration: 2.5, x: camPosition.camera.x, y: camPosition.camera.y, z: camPosition.camera.z, onComplete: () => {
  1076. closeDoorway(doorway)
  1077. }
  1078. })
  1079. gsap.to(game.lookAtFocus, { duration: 2.5, x: camPosition.focus.x, y: camPosition.focus.y, z: camPosition.focus.z })
  1080. stageData.currentRoom = ROOM_SHOP
  1081. shopTutorialPrompt(stageData)
  1082. }
  1083. function moveToBrew() {
  1084. let doorwayToOpen = doorway
  1085. if (stageData.currentRoom == ROOM_MARKET) {
  1086. doorwayToOpen = doorway2
  1087. }
  1088. openDoorway(doorwayToOpen)
  1089. const camPosition = stageData.cameraPositions[1]
  1090. gsap.to(game.camera.position, {
  1091. duration: 2.5, x: camPosition.camera.x, y: camPosition.camera.y, z: camPosition.camera.z, onComplete: () => {
  1092. closeDoorway(doorwayToOpen)
  1093. }
  1094. })
  1095. gsap.to(game.lookAtFocus, { duration: 2.5, x: camPosition.focus.x, y: camPosition.focus.y, z: camPosition.focus.z })
  1096. stageData.currentRoom = ROOM_BREW
  1097. brewTutorialPrompt(stageData)
  1098. }
  1099. function moveToMarket() {
  1100. closeBrewUI(game, stageData)
  1101. openDoorway(doorway2)
  1102. const camPosition = stageData.cameraPositions[2]
  1103. gsap.to(game.camera.position, {
  1104. duration: 2.5, x: camPosition.camera.x, y: camPosition.camera.y, z: camPosition.camera.z, onComplete: () => {
  1105. closeDoorway(doorway2)
  1106. }
  1107. })
  1108. gsap.to(game.lookAtFocus, { duration: 2.5, x: camPosition.focus.x, y: camPosition.focus.y, z: camPosition.focus.z })
  1109. stageData.currentRoom = ROOM_MARKET
  1110. }
  1111. export function moveToRoom(roomId) {
  1112. if (roomId == stageData.currentRoom) {
  1113. return
  1114. }
  1115. switch (roomId) {
  1116. case ROOM_SHOP:
  1117. moveToShop()
  1118. break;
  1119. case ROOM_BREW:
  1120. moveToBrew()
  1121. break;
  1122. case ROOM_MARKET:
  1123. moveToMarket()
  1124. break;
  1125. }
  1126. }
  1127. export function onKeyPress() {
  1128. if (game.keyboard['Digit1'] && stageData.currentRoom == ROOM_BREW) {
  1129. moveToRoom(ROOM_SHOP)
  1130. }
  1131. if (game.keyboard['Digit2'] && stageData.currentRoom != ROOM_BREW) {
  1132. moveToRoom(ROOM_BREW)
  1133. }
  1134. if (game.keyboard['Digit3'] && stageData.currentRoom == ROOM_BREW) {
  1135. moveToRoom(ROOM_MARKET)
  1136. }
  1137. if (game.keyboard['Digit9'] || game.keyboard['F9']) {
  1138. game.orbitControls.enabled = !game.orbitControls.enabled
  1139. if (!game.orbitControls.enabled) {
  1140. console.log(`position:`, game.camera.position)
  1141. console.log(`focus:`, game.orbitControls.target)
  1142. moveToRoom(stageData.currentRoom)
  1143. }
  1144. }
  1145. if (game.keyboard['Escape']) {
  1146. closeBrewUI(game, stageData)
  1147. closeShopUI(game, stageData)
  1148. hideNavigationUI(game, stageData)
  1149. openMainMenuUI(game, stageData)
  1150. hideGameStatusUI(game, stageData)
  1151. stageData.currentRoom = ROOM_BREW
  1152. game.camera.position.copy(stageData.cameraPositions[3].camera)
  1153. game.lookAtFocus = stageData.cameraPositions[3].focus.clone()
  1154. }
  1155. }