ShopState.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { Camera } from "../../libraries/Camera.js";
  2. import { DomButton } from "../../libraries/components/DomButton.js";
  3. import { TextPop } from "../../libraries/components/TextPop.js";
  4. import { Point } from "../../libraries/spatial/Point.js";
  5. import { Tween, TweenManager, Easing } from "../../libraries/Tween.js";
  6. import { Theme } from "../../libraries/components/Theme.js"
  7. import { Picture } from "../../libraries/CanvasArtist.js";
  8. import { Save } from "../../libraries/Save.js";
  9. import ShopCard from "../../libraries/ShopCard.js";
  10. import "../../libraries/RoundRectPolyfill.js"
  11. export class ShopState {
  12. constructor(view) {
  13. this.scaledCanvas = view.scaledCanvas
  14. this.stateMachine = view.stateMachine
  15. this.camera = new Camera()
  16. this.camera.scale = new Point(1, 1)
  17. this.tweenManager = new TweenManager()
  18. this.textPops = []
  19. this.closeShopButton = new DomButton(50, 50, view.element, "close shop", "close-shop")
  20. this.scrollStart = new Point(0, 0)
  21. this.scrollEnd = new Point(0, 0)
  22. this.scrollOffset = new Point(0, 128)
  23. this.save = new Save()
  24. this.shopCards = []
  25. for (let i = 0; i < Math.floor(16 * Math.random()) + 5; i++) {
  26. this.shopCards.push(new ShopCard())
  27. }
  28. this.cardHeight = 100
  29. }
  30. init(scaledCanvas) {
  31. this.canvasBounds = scaledCanvas.bounds
  32. }
  33. draw(ctx, scaledCanvas) {
  34. let fontScale = this.canvasBounds.width / 500
  35. let headerFontSize = Math.min(Math.floor(48 * fontScale), 48)
  36. ctx.save()
  37. this.cardHeight = Math.max(this.canvasBounds.height / 6, 100)
  38. ctx.translate(0, this.scrollOffset.y + (this.scrollEnd.y - this.scrollStart.y))
  39. for (let i = 0; i < this.shopCards.length; i++) {
  40. let card = this.shopCards[i]
  41. ctx.save()
  42. ctx.translate(0, this.getListRowOffset(this.cardHeight, i))
  43. card.draw(ctx, { cardHeight: this.cardHeight, screenWidth: this.canvasBounds.width, fontScale })
  44. ctx.restore()
  45. }
  46. ctx.restore()
  47. ctx.fillStyle = "gray"
  48. ctx.beginPath()
  49. ctx.rect(0, 0, this.canvasBounds.width, 128)
  50. ctx.fill()
  51. ctx.fillStyle = Theme.Colors.ivory
  52. ctx.font = `${headerFontSize}px ${Theme.Fonts.Header}`
  53. ctx.textAlign = "center"
  54. ctx.fillText("Bone Market", this.canvasBounds.width / 2, headerFontSize + 24)
  55. ctx.save()
  56. ctx.translate(16, 100)
  57. let inventorySpacing = 0
  58. if (this.getNumberOfGatheredSkulls() > 8) {
  59. ctx.save()
  60. ctx.scale(0.4, 0.4)
  61. ctx.translate(Theme.Game.TileSize.width / 2, Theme.Game.TileSize.height / 2)
  62. Picture.Skull(ctx, { width: Theme.Game.TileSize.width, height: Theme.Game.TileSize.height })
  63. ctx.fillStyle = Theme.Colors.ivory
  64. ctx.fillText(`x${this.getNumberOfGatheredSkulls()}`, 2 * Theme.Game.TileSize.width - (Theme.Game.TileSize.width / 2), Theme.Game.TileSize.height / 2)
  65. ctx.restore()
  66. inventorySpacing += 3
  67. } else {
  68. for (let i = 0; i < this.getNumberOfGatheredSkulls(); i++) {
  69. ctx.save()
  70. ctx.scale(0.4, 0.4)
  71. ctx.translate((i * (Theme.Game.TileSize.width + Theme.Game.TileSize.spacing) + (Theme.Game.TileSize.width / 2)), (Theme.Game.TileSize.height / 2))
  72. Picture.Skull(ctx, { width: Theme.Game.TileSize.width, height: Theme.Game.TileSize.height })
  73. ctx.restore()
  74. inventorySpacing++
  75. }
  76. }
  77. if (this.getNumberOfGatheredBones() > 8) {
  78. ctx.save()
  79. ctx.scale(0.4, 0.4)
  80. ctx.translate(((inventorySpacing) * (Theme.Game.TileSize.width + Theme.Game.TileSize.spacing)), 0)
  81. Picture.Bone(ctx, { width: Theme.Game.TileSize.width, height: Theme.Game.TileSize.height })
  82. ctx.fillStyle = Theme.Colors.ivory
  83. ctx.fillText(`x${this.getNumberOfGatheredBones()}`, 2 * Theme.Game.TileSize.width, Theme.Game.TileSize.height)
  84. ctx.restore()
  85. } else {
  86. for (let i = 0; i < this.getNumberOfGatheredBones(); i++) {
  87. ctx.save()
  88. ctx.scale(0.4, 0.4)
  89. ctx.translate(((i + inventorySpacing) * (Theme.Game.TileSize.width + Theme.Game.TileSize.spacing)), 0)
  90. Picture.Bone(ctx, { width: Theme.Game.TileSize.width, height: Theme.Game.TileSize.height })
  91. ctx.restore()
  92. }
  93. }
  94. ctx.fillStyle = Theme.Colors.darkgreen
  95. ctx.textAlign = "right"
  96. ctx.font = `${Math.min(Math.floor(24 * fontScale), 48)}px ${Theme.Fonts.Header}`
  97. ctx.fillText(`$${this.getDollars()}`, this.canvasBounds.width - 48,0)
  98. ctx.restore()
  99. this.camera.draw(ctx, scaledCanvas, () => {
  100. ctx.font = `${Math.min(Math.floor(48 * fontScale), 48)}px ${Theme.Fonts.Header}`
  101. ctx.textAlign = "center"
  102. let metrics = ctx.measureText(this.completeText)
  103. ctx.fillStyle = Theme.Colors.umber
  104. ctx.beginPath()
  105. ctx.roundRect(this.completePosition.x - metrics.width / 2, this.completePosition.y - Math.min(Math.floor(48 * fontScale), 48), metrics.width, Math.min(Math.floor(64 * fontScale), 64), 8)
  106. ctx.fill()
  107. ctx.fillStyle = Theme.Colors.ivory
  108. ctx.fillText(this.completeText, this.completePosition.x, this.completePosition.y)
  109. this.textPops.forEach(textpop => {
  110. textpop.draw(ctx, scaledCanvas)
  111. })
  112. })
  113. this.closeShopButton.setPosition(this.canvasBounds.width * (6 / 8), this.canvasBounds.height * (7 / 8))
  114. this.closeShopButton.draw(ctx, scaledCanvas)
  115. }
  116. getListRowOffset(height, i) {
  117. return 24 + (i * height + i * 8)
  118. }
  119. getNumberOfGatheredBones() {
  120. return this.save.getKey("bones")
  121. }
  122. getNumberOfGatheredSkulls() {
  123. return this.save.getKey("skulls")
  124. }
  125. getDollars() {
  126. let money = this.save.getKey("money")
  127. if(money == null) {
  128. this.save.setKey("money", 0)
  129. money = 0
  130. }
  131. return money
  132. }
  133. update(delta) {
  134. this.camera.update(delta)
  135. this.closeShopButton.update(delta)
  136. this.tweenManager.update()
  137. for (let i = 0; i < this.shopCards.length; i++) {
  138. let card = this.shopCards[i]
  139. card.update(delta, this.canvasBounds)
  140. }
  141. if (this.shopCards.length == 0 ||
  142. this.shopCards.filter((card) => card.item == "bones" && card.amount <= this.getNumberOfGatheredBones()).length == 0 &&
  143. this.shopCards.filter((card) => card.item == "skulls" && card.amount <= this.getNumberOfGatheredSkulls()).length == 0 ) {
  144. if (this.isComplete) {
  145. return
  146. }
  147. this.isComplete = true
  148. this.completeText = "Sold Out!"
  149. this.tweenManager.add(new Tween(this.completePosition, { y: 0 }, 2000, Easing.Elastic.EaseOut, () => {
  150. this.stateMachine.transitionTo("upgrade")
  151. }))
  152. }
  153. }
  154. enter() {
  155. this.registeredEvents = {}
  156. this.registeredEvents["resize"] = this.onResize.bind(this)
  157. this.registeredEvents["keydown"] = this.onKeyDown.bind(this)
  158. this.registeredEvents["keyup"] = this.onKeyUp.bind(this)
  159. this.registeredEvents["touchstart"] = this.onTouchStart.bind(this)
  160. this.registeredEvents["touchmove"] = this.onTouchMove.bind(this)
  161. this.registeredEvents["touchend"] = this.onTouchEnd.bind(this)
  162. this.registeredEvents["mousedown"] = this.onMouseDown.bind(this)
  163. this.registeredEvents["mousemove"] = this.onMouseMove.bind(this)
  164. this.registeredEvents["mouseup"] = this.onMouseUp.bind(this)
  165. this.registeredEvents["wheel"] = this.onMouseWheel.bind(this)
  166. for (let index in this.registeredEvents) {
  167. window.addEventListener(index, this.registeredEvents[index])
  168. }
  169. this.closeShopButton.attach()
  170. this.closeShopButton.onClick(() => {
  171. this.stateMachine.transitionTo("upgrade")
  172. })
  173. this.scrollStart = new Point(0, 0)
  174. this.scrollEnd = new Point(0, 0)
  175. this.scrollOffset = new Point(0, 128)
  176. this.save = new Save()
  177. this.shopCards = []
  178. for (let i = 0; i < Math.floor(16 * Math.random()) + 5; i++) {
  179. this.shopCards.push(new ShopCard())
  180. }
  181. this.completePosition = new Point(0, -1000)
  182. this.completeText = "Sold Out!"
  183. this.isComplete = false
  184. }
  185. leave() {
  186. for (let index in this.registeredEvents) {
  187. window.removeEventListener(index, this.registeredEvents[index])
  188. }
  189. this.closeShopButton.remove()
  190. this.textPops = []
  191. }
  192. onResize() {
  193. }
  194. onKeyDown(event) {
  195. }
  196. onKeyUp(event) {
  197. switch (event.code) {
  198. case "Enter":
  199. this.stateMachine.transitionTo("upgrade")
  200. break
  201. }
  202. }
  203. onTouchStart(event) {
  204. this.scrollStart = new Point(event.touches[0].clientX, event.touches[0].clientY)
  205. this.scrollEnd = new Point(event.touches[0].clientX, event.touches[0].clientY)
  206. this.isScrolling = true
  207. }
  208. onTouchMove(event) {
  209. if (!this.isScrolling) { return }
  210. this.scrollEnd.x = event.touches[0].clientX
  211. this.scrollEnd.y = event.touches[0].clientY
  212. }
  213. onTouchEnd(event) {
  214. this.isScrolling = false
  215. this.scrollOffset.x += this.scrollEnd.x - this.scrollStart.x
  216. this.scrollOffset.y += this.scrollEnd.y - this.scrollStart.y
  217. this.scrollOffset.y = this.getScrollLimits()
  218. this.scrollStart.x = 0
  219. this.scrollStart.y = 0
  220. this.scrollEnd.x = 0
  221. this.scrollEnd.y = 0
  222. }
  223. getScrollLimits() {
  224. return Math.min(Math.max(this.scrollOffset.y, (-(this.cardHeight * (this.shopCards.length + 1)) + this.canvasBounds.height) - 128), 128)
  225. }
  226. onMouseDown(event) {
  227. this.scrollStart = new Point(event.clientX, event.clientY)
  228. this.scrollEnd = new Point(event.clientX, event.clientY)
  229. this.isScrolling = true
  230. }
  231. onMouseMove(event) {
  232. document.body.style.cursor = "default"
  233. for (let i = 0; i < this.shopCards.length; i++) {
  234. let card = this.shopCards[i]
  235. let offset = this.scrollOffset.y + this.getListRowOffset(this.cardHeight, i)
  236. card.mouseMove(event, offset)
  237. }
  238. if (!this.isScrolling) { return }
  239. this.scrollEnd.x = event.clientX
  240. this.scrollEnd.y = event.clientY
  241. }
  242. onMouseUp(event) {
  243. this.isScrolling = false
  244. this.scrollOffset.x += this.scrollEnd.x - this.scrollStart.x
  245. this.scrollOffset.y += this.scrollEnd.y - this.scrollStart.y
  246. this.scrollOffset.y = this.getScrollLimits()
  247. this.scrollStart.x = 0
  248. this.scrollStart.y = 0
  249. this.scrollEnd.x = 0
  250. this.scrollEnd.y = 0
  251. let cardsToRemove = []
  252. for (let i = 0; i < this.shopCards.length; i++) {
  253. let card = this.shopCards[i]
  254. card.onClick((item, rate, amount) => {
  255. switch (item) {
  256. case "bones":
  257. if (this.getNumberOfGatheredBones() < amount) {
  258. let pop = new TextPop(this.tweenManager, `No Sale`, new Point(0, 0), 0)
  259. this.textPops.push(pop)
  260. return
  261. }
  262. this.save.incrementKey("bones", -amount)
  263. break;
  264. case "skulls":
  265. if (this.getNumberOfGatheredSkulls() < amount) {
  266. let pop = new TextPop(this.tweenManager, `No Sale`, new Point(0, 0), 0)
  267. this.textPops.push(pop)
  268. return
  269. }
  270. this.save.incrementKey("skulls", -amount)
  271. break;
  272. }
  273. this.save.incrementKey("money", rate * amount)
  274. let pop = new TextPop(this.tweenManager, `$${rate * amount}`, new Point(0, 0), 0)
  275. this.textPops.push(pop)
  276. cardsToRemove.push(card)
  277. if(this.save.getKey("story-stage") == 0) {
  278. this.save.setKey("story-stage", 1)
  279. }
  280. })
  281. }
  282. this.shopCards = this.shopCards.filter((card) => !cardsToRemove.includes(card))
  283. }
  284. onMouseWheel(event) {
  285. this.scrollOffset.y += event.wheelDeltaY
  286. this.scrollOffset.y = this.getScrollLimits()
  287. }
  288. }