From 11842fa54ab9ff4c5d54384cf427be6db50bb5d2 Mon Sep 17 00:00:00 2001 From: alesan99 Date: Mon, 1 Apr 2024 13:44:44 -0500 Subject: [PATCH] Finish Quest Menu (Just missing complete quests) Add Scrollbars Improve buttons Improve ItemGrid Add quest "area" progression --- website/game/gui/gui.js | 330 +++++++++++++++++++---------- website/game/gui/itemgrid.js | 48 ++--- website/game/menu/chat.js | 7 +- website/game/menu/customization.js | 9 +- website/game/menu/quests.js | 205 +++++++++++++----- website/game/quests.js | 7 + website/game/world.js | 3 + 7 files changed, 415 insertions(+), 194 deletions(-) diff --git a/website/game/gui/gui.js b/website/game/gui/gui.js index f24b2d2..cea900b 100644 --- a/website/game/gui/gui.js +++ b/website/game/gui/gui.js @@ -1,115 +1,221 @@ class Button { - constructor(label="", action, graphic, x=0, y=0, w, h) { //in px, label is text on button, action is function to call when clicked - this.visible = true; - this.label = label; - this.labelJustify = "center"; - - if (graphic) { - // If specified, render image for button with frames - if (graphic.image) { - this.image = graphic.image; - this.frames = graphic.frames; - } - if (graphic.icon) { - this.icon = graphic.icon; - this.iconFrame = graphic.iconFrame; - } - if (graphic.visible != null) { - this.visible = graphic.visible; - } - } - this.action = action; - - this.x = x; - this.y = y; - this.w = w; - this.h = h; - - this.hover = false; - this.holding = false; - this.selected = false; - } - - checkMouseInside(){ - let [mouseX, mouseY] = getMousePos(); //returns x and y pos of mouse - if (mouseX > this.x && mouseX < this.x + this.w && mouseY > this.y && mouseY < this.y + this.h) { - return true; - } - return false; - } - update(dt){ - this.hover = this.checkMouseInside(); - if (this.hover) { - CURSOR.on = true; - } - } - - click(){ - this.hover = this.checkMouseInside(); - if (this.hover) {//this should only click if you're hovering over the button - this.holding = true - return true - } - } - clickRelease(){ - if (this.holding == true){ - this.holding = false; - this.action(); - } - } - - draw(){ - if (!this.visible) { - // Don't render if button was specified to be not visible - return false - } - - if (this.image) { - // Render image for button - let frame = 0 - if (this.holding == true){ - frame = 2 - } else if (this.hover == true){ - frame = 1 - } - DRAW.setColor(255,255,255,1) - DRAW.image(this.image,this.frames[frame], this.x+this.w/2, this.y+this.h/2, 0, 1,1, 0.5,0.5) - } else { - // Render button with basic rectangles if no image was provided - if (this.holding == true){ - DRAW.setColor(216,151,91,1); //dark - } else if (this.hover == true){ - DRAW.setColor(248,222,187,1); //medium - } else if (this.selected == true){ - DRAW.setColor(242,161,99,1); //dark - } else { - DRAW.setColor(242,199,140,1); //light - } - - DRAW.rectangle(this.x, this.y, this.w, this.h); - DRAW.setColor(168, 85, 38, 1) - DRAW.setLineWidth(2) - DRAW.rectangle(this.x, this.y, this.w, this.h, "line"); - } - - if (this.icon) { - // Icon - DRAW.setColor(255,255,255,1) - DRAW.image(this.icon,this.iconFrame, this.x+this.w/2, this.y+this.h/2, 0, 1,1, 0.5,0.5) - } else if (this.label) { - // Label - DRAW.setFont(FONT.guiLabel) - DRAW.setColor(112, 50, 16,1) - if (this.labelJustify == "center") { - DRAW.text(this.label, this.x+this.w/2, this.y+this.h/2+7, this.labelJustify) - } else if (this.labelJustify == "left") { - DRAW.text(this.label, this.x+10, this.y+this.h/2+7, this.labelJustify) - } else if (this.labelJustify == "right") { - DRAW.text(this.label, this.x+this.w-10, this.y+this.h/2+7, this.labelJustify) - } - } - } + constructor(label="", action=()=>{}, graphic, x=0, y=0, w, h) { //in px, label is text on button, action is function to call when clicked + this.visible = true; + this.label = label; + this.labelJustify = "center"; + + if (graphic) { + // If specified, render image for button with frames + if (graphic.image) { + this.image = graphic.image; + this.frames = graphic.frames; + } + if (graphic.icon) { + this.icon = graphic.icon; + this.iconFrame = graphic.iconFrame; + } + if (graphic.visible != null) { + this.visible = graphic.visible; + } + } + this.action = action; + + this.x = x; + this.y = y; + this.w = w; + this.h = h; + + this.hover = false; + this.holding = false; + this.selected = false; + } + + checkMouseInside(){ + let [mouseX, mouseY] = getMousePos(); //returns x and y pos of mouse + if (mouseX > this.x && mouseX < this.x + this.w && mouseY > this.y && mouseY < this.y + this.h) { + return true; + } + return false; + } + update(dt){ + this.hover = this.checkMouseInside(); + if (this.hover) { + CURSOR.on = true; + } + } + + click(button, x, y){ + this.hover = this.checkMouseInside(); + if (this.hover) {//this should only click if you're hovering over the button + this.holding = true + return true + } + } + clickRelease(button, x, y){ + if (this.holding == true){ + this.holding = false; + this.action(); + } + } + + draw(){ + if (!this.visible) { + // Don't render if button was specified to be not visible + return false + } + + if (this.image) { + // Render image for button + let frame = 0 + if (this.holding == true){ + frame = 2 + } else if (this.hover == true){ + frame = 1 + } + DRAW.setColor(255,255,255,1) + DRAW.image(this.image,this.frames[frame], this.x+this.w/2, this.y+this.h/2, 0, 1,1, 0.5,0.5) + } else { + // Render button with basic rectangles if no image was provided + if (this.holding == true){ + DRAW.setColor(216,151,91,1); //dark + } else if (this.hover == true){ + DRAW.setColor(248,222,187,1); //medium + } else if (this.selected == true){ + DRAW.setColor(242,161,99,1); //dark + } else { + DRAW.setColor(242,199,140,1); //light + } + + DRAW.rectangle(this.x, this.y, this.w, this.h); + DRAW.setColor(168, 85, 38, 1) + DRAW.setLineWidth(2) + DRAW.rectangle(this.x+1, this.y+1, this.w-2, this.h-2, "line"); + + DRAW.setColor(255,255,255, 0.4); + DRAW.line(this.x+3, this.y+4, this.x+this.w-3, this.y+4); // Highlight + } + + if (this.icon) { + // Icon + DRAW.setColor(255,255,255,1) + DRAW.image(this.icon,this.iconFrame, this.x+this.w/2, this.y+this.h/2, 0, 1,1, 0.5,0.5) + } else if (this.label) { + // Label + DRAW.setFont(FONT.guiLabel) + DRAW.setColor(112, 50, 16,1) + if (this.labelJustify == "center") { + DRAW.text(this.label, this.x+this.w/2, this.y+this.h/2+7, this.labelJustify) + } else if (this.labelJustify == "left") { + DRAW.text(this.label, this.x+10, this.y+this.h/2+7, this.labelJustify) + } else if (this.labelJustify == "right") { + DRAW.text(this.label, this.x+this.w-10, this.y+this.h/2+7, this.labelJustify) + } + } + } } -//need to add states -//need callbacks, click, clickRelease \ No newline at end of file +// Scrollbar +class ScrollBar { + // position x, y, size w, h, min scroll, max scroll, "visible" window of scrolling area, scroll bar movement update function + constructor(x=0, y=0, w=20, h=100, min, max, window, updateFunc=(scroll)=>{}, scrollStep=20) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.updateFunc = updateFunc; + + this.buttonSize = this.w; + this.scrollStep = scrollStep; + this.upButton = new Button("^", ()=>{this.scrollButton(-this.scrollStep)}, null, this.x, this.y, this.buttonSize, this.buttonSize); + this.downButton = new Button("V", ()=>{this.scrollButton(this.scrollStep)}, null, this.x, this.y+this.h-this.buttonSize, this.buttonSize, this.buttonSize); + + this.barOriginY = this.y+this.buttonSize; + this.barTotalRange = this.h-this.buttonSize*2; + this.bar = new Button(null, ()=>{}, null, this.x, this.barOriginY, this.w, this.h-this.buttonSize*2); + this.barClickX = 0; + this.barClickY = 0; + + this.scroll = 0 + + this.updateRange(min, max, window); + } + + checkMouseInside(){ + let [mouseX, mouseY] = getMousePos(); //returns x and y pos of mouse + if (mouseX > this.x && mouseX < this.x + this.w && mouseY > this.y && mouseY < this.y + this.h) { + return true; + } + return false; + } + update(dt){ + this.hover = this.checkMouseInside(); + + let [mouseX, mouseY] = getMousePos(); + if (this.bar.holding) { + this.bar.y = Math.min(this.barOriginY+this.barRange, Math.max(this.barOriginY, (mouseY-this.barClickY))); + let newScroll = this.min + (this.max-this.min-this.window)*(this.bar.y-this.barOriginY)/(this.barRange); + this.updateScroll(newScroll); + } + + // Buttons + this.upButton.update(dt); + this.downButton.update(dt); + this.bar.update(dt); + } + draw() { + // Scroll Range background + DRAW.setColor(168, 85, 38, 1); + DRAW.rectangle(this.x, this.y+this.buttonSize, this.w, this.h-this.buttonSize*2, "fill"); + + // Buttons + this.upButton.draw(); + this.downButton.draw(); + this.bar.draw(); + } + + click(button, x, y){ + if (this.bar.click()) { + let [mouseX, mouseY] = getMousePos(); + this.barClickY = mouseY-this.bar.y; + } + return this.upButton.click() || this.downButton.click() + } + clickRelease(button, x, y){ + return this.upButton.clickRelease() || this.downButton.clickRelease() || this.bar.clickRelease() + } + mouseScroll(dy){ + if (dy > 0) { + this.scrollButton(this.scrollStep); + } else { + this.scrollButton(-this.scrollStep); + } + } + + updateRange(min, max, window=null) { + this.min = min; + this.max = max; + + // Update scroll bar size + if (window) { + this.window = window; + } + // Like a browser, the bar size is proportional to the content size + this.bar.h = Math.max(Math.min((this.window)/(this.max-this.min) * (this.h-this.buttonSize*2), (this.h-this.buttonSize*2)), 30); + this.barRange = this.barTotalRange-this.bar.h; + + // Keep scroll within new range + this.updateScroll(this.scroll); + } + + scrollButton(amount) { + this.updateScroll(this.scroll + amount); + } + + updateScroll(newScroll) { + // Keep scroll within new range + this.scroll = newScroll || 0; + this.scroll = Math.min(Math.max(this.min, this.max-this.window), Math.max(this.min, this.scroll)); + this.updateFunc(this.scroll); + this.bar.y = this.barOriginY + this.barRange*(this.scroll-this.min)/(this.max-this.min-this.window); + } +} \ No newline at end of file diff --git a/website/game/gui/itemgrid.js b/website/game/gui/itemgrid.js index 18c09f6..cf3a26d 100644 --- a/website/game/gui/itemgrid.js +++ b/website/game/gui/itemgrid.js @@ -32,8 +32,9 @@ class ItemGrid { // Vertical scroll this.scroll = 0 this.buttons = [] - this.buttons["scrollUp"] = new Button("", ()=>{this.scrollUp()}, null, this.x+this.w, this.y, 20, 20) - this.buttons["scrollDown"] = new Button("", ()=>{this.scrollDown()}, null, this.x+this.w, this.y+this.h-20, 20, 20) + this.buttons["scrollBar"] = new ScrollBar(this.x+this.w, this.y, 20, this.h, 0, this.list.length/this.gw, this.gh, (scroll)=>{this.updateScroll(scroll)}, 1) + //this.buttons["scrollUp"] = new Button("", ()=>{this.scrollUp()}, null, this.x+this.w, this.y, 20, 20) + //this.buttons["scrollDown"] = new Button("", ()=>{this.scrollDown()}, null, this.x+this.w, this.y+this.h-20, 20, 20) this.hover = false; this.holding = false; @@ -101,21 +102,10 @@ class ItemGrid { } } mouseScroll(dy){ - if (this.hover) { - if (dy < 0) { - this.scrollUp() - } else { - this.scrollDown() - } - } + this.buttons["scrollBar"].mouseScroll(dy) } - scrollUp(){ - this.scroll -= 1 - this.scroll = Math.max(0, Math.min(this.scroll, this.list.length/this.gw-this.gh)) - } - scrollDown(){ - this.scroll += 1 - this.scroll = Math.max(0, Math.min(this.scroll, this.list.length/this.gw-this.gh)) + updateScroll(scroll) { + this.scroll = Math.floor(scroll+0.5) } getCellIndex(cx, cy) { @@ -129,12 +119,26 @@ class ItemGrid { } } + updateList(list) { + this.list = list + this.scroll = 0 + this.buttons["scrollBar"].updateRange(0, this.list.length/this.gw, null) + } + draw(){ + // Scroll bar + DRAW.setColor(168, 85, 38, 1); + DRAW.rectangle(this.x+this.w, this.y, 20, this.h, "fill"); + for (const button in this.buttons) { + this.buttons[button].draw() + } + // Render all grid cells let [mouseX, mouseY] = getMousePos(); //returns x and y pos of mouse DRAW.setColor(242, 242, 242, 1); // Light color for other cells DRAW.rectangle(this.x, this.y, this.w, this.h); + DRAW.setFont(FONT.guiLabel); for (let cx = 0; cx < this.gw; cx++) { for (let cy = 0; cy < this.gh; cy++) { @@ -201,17 +205,13 @@ class ItemGrid { if (this.list[i]) { let itemType = getItemCategory(this.list[i]) if (itemType && ITEMS[itemType][this.list[i]]) { // Make sure item has been loaded + let name = ITEMS[itemType][this.list[i]].name + DRAW.setColor(255,255,255, 0.5) + DRAW.rectangle(mouseX+20, mouseY, DRAW.getTextWidth(name), 24, "fill") DRAW.setColor(0, 0, 0, 1) - DRAW.text(ITEMS[itemType][this.list[i]].name, mouseX+20, mouseY+20, "left") + DRAW.text(name, mouseX+20, mouseY+18, "left") } } } - - // Scroll bar - DRAW.setColor(168, 85, 38, 1); - DRAW.rectangle(this.x+this.w, this.y, 20, this.h, "fill"); - for (const button in this.buttons) { - this.buttons[button].draw() - } } } \ No newline at end of file diff --git a/website/game/menu/chat.js b/website/game/menu/chat.js index b9bd890..bb5f51f 100644 --- a/website/game/menu/chat.js +++ b/website/game/menu/chat.js @@ -58,6 +58,7 @@ MENUS["chatMenu"] = new class extends Menu { break case "/give": addItem(null, arg, arg2 || 1) + break case "/head": PROFILE.head = arg PLAYER.updateProfile(PROFILE, "sendToServer") @@ -95,7 +96,11 @@ MENUS["chatMenu"] = new class extends Menu { QuestSystem.debug() break case "/quest": // Force progress in quest - QuestSystem.progress(arg, Number(arg2) || 0, Number(arg3) || 1) + if (!QuestSystem.getQuest(arg)) { + QuestSystem.start(arg) + } else { + QuestSystem.progress(arg, Number(arg2) || 0, Number(arg3) || 1) + } break case "/debug": // Debug physics DEBUGPHYSICS = !DEBUGPHYSICS diff --git a/website/game/menu/customization.js b/website/game/menu/customization.js index feb4b0b..668fb66 100644 --- a/website/game/menu/customization.js +++ b/website/game/menu/customization.js @@ -47,9 +47,6 @@ MENUS["customization"] = new class extends Menu { this.buttons["furnitureTab"] = new Button("FT", ()=>{this.filterInventory("furniture"); this.buttons[this.tab].selected=false; this.tab = "furnitureTab"; this.buttons[this.tab].selected=true}, {icon:IMG.items, iconFrame:SPRITE.items.getFrame(3)}, 522+34*3,150, 34,34) this.buttons["itemTab"] = new Button("I", ()=>{this.filterInventory("item"); this.buttons[this.tab].selected=false; this.tab = "itemTab"; this.buttons[this.tab].selected=true}, {icon:IMG.items, iconFrame:SPRITE.items.getFrame(4)}, 522+34*4,150, 34,34) this.buttons["petTab"] = new Button("P", ()=>{this.filterInventory("pet"); this.buttons[this.tab].selected=false; this.tab = "petTab"; this.buttons[this.tab].selected=true}, {icon:IMG.items, iconFrame:SPRITE.items.getFrame(5)}, 522+34*5,150, 34,34) - this.filter = "all" - this.filterInventory("all") - this.buttons["allTab"].selected = true this.buttons["inventory"] = new ItemGrid( (itemId,itemType)=>{ @@ -92,6 +89,11 @@ MENUS["customization"] = new class extends Menu { }, 476,184, 56,56, 5,3) this.buttons["inventory"].showCount = true // How how many of each item the player owns + // Do initial filter + this.filter = "all" + this.filterInventory("all") + this.buttons["allTab"].selected = true + // this.buttons["bodyRight"] = new Button(">", ()=>{ // let keys = Object.keys(SAVEDATA.items.body); // if (keys.length == 0) { @@ -119,6 +121,7 @@ MENUS["customization"] = new class extends Menu { this.inventory.push(itemId) } } + this.buttons["inventory"].updateList(this.inventory) } draw() { diff --git a/website/game/menu/quests.js b/website/game/menu/quests.js index 0f9833b..85f64df 100644 --- a/website/game/menu/quests.js +++ b/website/game/menu/quests.js @@ -12,20 +12,26 @@ MENUS["questsMenu"] = new class extends Menu { this.buttons = {} this.buttons["close"] = new Button("X", ()=>{closeMenu()}, null, 740,128, 32,32) - - this.buttons["sort:all"] = new Button("All", ()=>{this.sortQuests("all")}, null, 255,148, 100,32) - this.buttons["sort:incomplete"] = new Button("Incomplete", ()=>{this.sortQuests("incomplete")}, null, 360,148, 100,32) - this.buttons["sort:complete"] = new Button("Complete", ()=>{this.sortQuests("complete")}, null, 465,148, 100,32) - + + // Quest display list this.listX = this.x+20 // X position of list - this.listY = this.y+82 // Y position of list - this.listW = this.w-40 // Width of list - this.listH = this.h-100 // Height of list + this.listY = this.y+77 // Y position of list + this.listW = this.w-40-20 // Width of list + this.listH = this.h-110 // Height of list this.listEntryH = 26 // Height of each entry this.listScroll = 0 // Scroll position of list + this.listDisplayLen = Math.floor(this.listH/this.listEntryH) // Number of entries that can be displayed at once this.list = [] // What is currently being displayed in menu (Because some quests may be expanded, or sorted out) - this.quests = [] + // Sorting Buttons + this.buttons["sort:all"] = new Button("All", ()=>{this.sortQuests("all")}, null, 254,148, 110,32) + this.buttons["sort:incomplete"] = new Button("Incomplete", ()=>{this.sortQuests("incomplete")}, null, 369,148, 110,32) + this.buttons["sort:complete"] = new Button("Complete", ()=>{this.sortQuests("complete")}, null, 484,148, 110,32) + + this.buttons["scrollBar"] = new ScrollBar(this.listX+this.listW, this.listY, 20, this.listH, 0, 100, this.listH/this.listEntryH, (scroll)=>{this.updateScroll(scroll)}, 1) + this.buttons["scrollBar"].updateRange(0, this.list.length, null) + + this.quests = [] // Sorted quest info list this.sorted = "all" this.sortQuests(this.sorted) } @@ -52,35 +58,100 @@ MENUS["questsMenu"] = new class extends Menu { this.quests.push(QuestSystem.getQuest(questName)) } } - this.generateList() + this.generateList("refresh") } // Generate list of quests and progress to display - generateList() { - this.list = [] - let i = 0 - for (let quest of this.quests) { - let expandButton = new Button(quest.name, ()=>{this.toggleList(i)}, null, this.listX, this.listY+this.listEntryH*i, this.listW, this.listEntryH) - expandButton.labelJustify = "left" - let entry = { - type: "quest", // quest, progress, description - expanded: false, - button: expandButton, + generateList(refresh) { + if (refresh) { + // Create list from scratch + this.list = [] + let i = 0 + for (let quest of this.quests) { + let expandButton = new Button(quest.name, ()=>{this.toggleQuest(quest.name)}, null, this.listX, this.listY+this.listEntryH*i, this.listW, this.listEntryH) + expandButton.labelJustify = "left" + let entry = { + type: "quest", // quest, progress, description + quest: this.quests[i], + questi: i, + expanded: false, + button: expandButton, + } + this.list.push(entry) + i += 1 + } + } else { + // Update existing list + // Iterate through list and remove entries that have been collapsed or add new entries that have been expanded + let questAppearsExpanded = false // Does the quest entry have progress entries after it? + for (let i=this.list.length-1; i>=0; i--) { + let entry = this.list[i] + if (entry.type == "quest") { + if (questAppearsExpanded !== false) { + // Remove collapsed entries + if (!entry.expanded) { + this.list.splice(i+1, questAppearsExpanded-i) + } + } else { + // Add progress entries + if (entry.expanded) { + let quest = this.quests[entry.questi] + for (let j=0; j {loadAreaFile(data, this.area, this.oldArea, fromWarp, endFunc)}) + + // Progress Quests + QuestSystem.event("area", area) // Progress quests that look for areas } // Register an object as part of the physics world