game.js 59 KB

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