From 830ff078b2cb8fdeeae0c0b4a97c3c6794e41c14 Mon Sep 17 00:00:00 2001 From: xangelo Date: Wed, 23 Oct 2024 12:25:11 -0400 Subject: [PATCH] fix: enhance durability visibility Durability used to be present as a progress bar on the item, but made it hard to view durability on equipped items. Durability is now present in the title attribute, and there's a visual gradient applied to the items to denote how "damaged" it is --- migrations/20241021184333_equipment-exp.ts | 20 ++ public/assets/css/game.css | 263 +++++++++++++++++++-- src/server/inventory.ts | 2 + src/server/views/inventory.ts | 27 +-- src/shared/equipped.ts | 7 - src/shared/inventory.ts | 42 ++++ src/shared/utils.ts | 9 + 7 files changed, 327 insertions(+), 43 deletions(-) create mode 100644 migrations/20241021184333_equipment-exp.ts create mode 100644 src/shared/utils.ts diff --git a/migrations/20241021184333_equipment-exp.ts b/migrations/20241021184333_equipment-exp.ts new file mode 100644 index 0000000..cc2da58 --- /dev/null +++ b/migrations/20241021184333_equipment-exp.ts @@ -0,0 +1,20 @@ +import { Knex } from "knex"; + + +export async function up(knex: Knex): Promise { + return knex.schema.alterTable('inventory', t => { + t.timestamps(); + t.integer('current_exp').defaultTo(0); + t.integer('current_level').defaultTo(1); + }); +} + + +export async function down(knex: Knex): Promise { + return knex.schema.alterTable('inventory', t => { + t.dropTimestamps(); + t.dropColumn('current_exp'); + t.dropColumn('current_level'); + }); +} + diff --git a/public/assets/css/game.css b/public/assets/css/game.css index 2f32e19..05fc248 100644 --- a/public/assets/css/game.css +++ b/public/assets/css/game.css @@ -1,11 +1,83 @@ @font-face { - font-family: 'Breathe Fire'; - src: url('/assets/font/BreatheFire.woff2') format('woff2'), - url('/assets/font/BreatheFire.woff') format('woff'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Breathe Fire'; + src: url('/assets/font/BreatheFire.woff2') format('woff2'), + url('/assets/font/BreatheFire.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; } + +.equipment-slot { + position: relative; +} + +.inventory-icon::after, +.equipment-slot::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 100%; + pointer-events: none; +} + +.equipment-slot.perfect::after, +.inventory-icon.perfect::after { + background: linear-gradient(to top, + rgba(0, 255, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.great::after, +.inventory-icon.great::after { + background: linear-gradient(to top, + rgba(51, 204, 51, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.good::after, +.inventory-icon.good::after { + background: linear-gradient(to top, + rgba(102, 204, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.fair::after, +.inventory-icon.fair::after { + background: linear-gradient(to top, + rgba(255, 204, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.poor::after, +.inventory-icon.poor::after { + background: linear-gradient(to top, + rgba(255, 153, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.terrible::after, +.inventory-icon.terrible::after { + background: linear-gradient(to top, + rgba(255, 102, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + +.equipment-slot.about-to-break::after, +.inventory-icon.about-to-break::after { + background: linear-gradient(to top, + rgba(255, 0, 0, 0.5) 0%, + transparent 30%, + transparent 100%); +} + html { height: 100vh; } @@ -16,9 +88,11 @@ body { width: 100%; max-width: 724px; } + .title-font { font-family: 'Breathe Fire', monospace; } + #title-bar { background-color: transparent; margin-top: 0.5rem; @@ -28,6 +102,7 @@ body { justify-content: space-between; align-items: center; } + #title-bar a { font-size: 3rem; color: #8e4607; @@ -37,14 +112,17 @@ body { border-bottom: solid 4px; line-height: 25px; } + #time-of-day { text-align: right; } + #time-of-day img { width: 32px; vertical-align: middle; mix-blend-mode: color-burn; } + #view { font-size: 14px; padding: 1rem; @@ -53,18 +131,23 @@ body { background: #fffef0; background-image: url(data:image/png;base64,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); } + b { font-weight: bold; } + a { color: #a20b00; } + select { padding: 0.3rem; } + input { border: 1px solid #000; } + button { cursor: pointer; color: #fff; @@ -75,46 +158,59 @@ button { text-shadow: -1px -1px 0px rgba(0, 0, 0, 0.3); border: solid 1px #6d251c; } + button.red { background: #a20b00; } + button.red:hover { background: #b20b00; } + button.green { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==), linear-gradient(to bottom, #41d437 0%, #0a9404 100%); } + button.green:hover { background: url(data:image/png;base64,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), linear-gradient(to bottom, #32e027 0%, #10a209 100%); } + button:active { position: relative; top: 1px; } -button:disabled, button:disabled:hover { + +button:disabled, +button:disabled:hover { background: #aaa; cursor: not-allowed; } + button:focus { outline: none; } + .hidden { display: none !important; } + p { margin-bottom: 1rem; } + p:last-child { margin-bottom: 0; } -#announcements, #signup-prompt { +#announcements, +#signup-prompt { padding: 1rem; line-height: 1.2rem; border: solid 1px #000; background-color: #fff; margin-bottom: 1rem; } + #announcements { margin: 1rem 0; } @@ -128,10 +224,12 @@ p:last-child { height: 100%; backdrop-filter: blur(10px); } + dialog::backdrop { backdrop-filter: blur(3px); } + dialog { background-color: #fff; border: solid 1px #000; @@ -140,13 +238,16 @@ dialog { top: 20%; font-size: 0.9rem; } + dialog .modal-header { font-weight: bold; text-align: right; } + dialog .actions { text-align: right; } + dialog .close-modal { cursor: pointer; } @@ -155,7 +256,9 @@ dialog .close-modal { #signup { flex-direction: column; } - #signup .form-group, #signup button { + + #signup .form-group, + #signup button { margin-bottom: 1rem; } } @@ -165,23 +268,30 @@ dialog .close-modal { right: 0; bottom: 0; } + .alert { padding: 0.3rem; max-width: 17rem; line-height: 1.2rem; box-shadow: -3px -3px 4px 0px rgba(0, 0, 0, 0.5); } -.alert.success, button.success { + +.alert.success, +button.success { border: solid 1px #0a0; background-color: #def7e5; } -.alert.error, button.error { + +.alert.error, +button.error { border: solid 1px #a00; background-color: #f7dede; } + .success { color: #0a0; } + .error { color: #a00; } @@ -190,49 +300,64 @@ dialog .close-modal { max-width: 96px; min-width: 32px; } + #avatar { width: 100%; border: solid 1px #6d251c; } + header { display: flex; align-items: flex-start; border: 0; margin-bottom: 1rem; } + #player-info { width: 100%; } + #player-section { display: flex; } + #player-section div { flex-grow: 1; } + #player-section .gold { text-align: right; } -#stat-bars, #defender-stat-bars { + +#stat-bars, +#defender-stat-bars { width: 100%; margin: 0 5px; } -#stat-bars .progress-bar, #defender-stat-bars .progress-bar { + +#stat-bars .progress-bar, +#defender-stat-bars .progress-bar { margin-bottom: 2px; } + #stat-bars .gold { font-size: 0.7rem; } + #stat-bars .gold:after { content: 'G'; } + #ap-bar { display: flex; } -#ap-bar > div { + +#ap-bar>div { display: flex; flex-grow: 1; align-items: center; } + #ap-bar .icon { flex-basis: 16px; width: 16px; @@ -253,23 +378,29 @@ nav { text-align: center; padding: 1rem 0; } + nav li { display: inline-block; list-style: none; } + nav li:before { content: '['; } + nav li:after { content: ']'; } + nav a { text-decoration: none; } + nav a.active { font-weight: bold; text-decoration: underline; } + nav.filter { margin: 0; text-align: right; @@ -278,36 +409,44 @@ nav.filter { position: relative; bottom: 5px; } + nav.filter a { border-bottom-width: 0; z-index: 1; padding: 0.6rem; position: relative; } + nav.filter a.active { background-color: #fff; border: solid #6d251c; border-width: 1px 1px 0; z-index: 4; } + .filter-container .listing { border: solid 1px #6d251c; z-index: 2; position: relative; background-color: #fff; } + nav.filter-result { display: none; } + nav.filter-result.active { display: block !important; } + #inventory-section { width: 100%; } + #inventory-section .listing { top: 2px; } + .inventory-listing { min-height: 2rem; } @@ -315,6 +454,7 @@ nav.filter-result.active { .inventory-ITEMS { display: flex; } + .player-item { position: relative; cursor: pointer; @@ -324,12 +464,15 @@ nav.filter-result.active { height: 64px; overflow: hidden; } + .player-item img { filter: grayscale(40%); } + .player-item img:hover { filter: none; } + .player-item .amount { font-weight: bold; position: absolute; @@ -340,23 +483,28 @@ nav.filter-result.active { padding: 4px; border-radius: 3px 0 0 0; } + .item-modal-overview { display: flex; } + .item-modal-overview .icon { width: 64px; height: 64px; margin: 0 1rem 1rem 0; } + .item-modal-overview .icon img { width: 64px; height: 64px; } + .item-modal-overview .name { margin-top: 0; font-weight: bold; margin: 0 1rem 0.8rem 0; } + .item-modal-overview p { margin: 1rem; } @@ -364,13 +512,16 @@ nav.filter-result.active { .tab { display: none; } + .tab.active { display: block; } + #main-nav { margin-bottom: 1rem; position: relative; } + #main-nav section { min-height: 344px; } @@ -382,14 +533,18 @@ nav.filter-result.active { color: #fff; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==); } -.stat-breakdown th, .stat-breakdown td { + +.stat-breakdown th, +.stat-breakdown td { padding: 0.5rem; min-width: 100px; line-height: 1rem; } + .stat-breakdown tr:nth-child(even) { background-color: #c7b7a1; } + .increase-stat { padding: 1px 6px; } @@ -408,6 +563,7 @@ nav.filter-result.active { position: relative; z-index: 1; } + .city-title:before { position: absolute; content: ' '; @@ -420,6 +576,7 @@ nav.filter-result.active { border: solid 2px #ffa500; clip-path: polygon(100% 0, 95% 50%, 100% 98%, 0% 100%, 5% 50%, 0 0); } + .city-title { font-family: 'Breathe Fire', monospace; font-size: 1.5rem; @@ -438,26 +595,33 @@ nav.filter-result.active { #fight-container { margin: 0 auto; } + #defender-info { display: flex; width: 70%; margin: 0 auto 1rem; } + .monster-identifier { text-align: left; } + #defender-name { font-weight: bold; } + .Elder #defender-name { color: #2b2b2b; } + .Skittish #defender-name { color: #8700ff; } + .Brute #defender-name { color: #a91313; } + #fight-results { margin-top: 1rem; } @@ -468,8 +632,9 @@ nav.filter-result.active { margin: 0rem auto 1rem; padding: 3rem 2rem 2rem; line-height: 1.3rem; - background: linear-gradient(to bottom, rgba(255,255,255, 0) 0%, rgba(255, 255, 255, 0.5) 30%); + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.5) 30%); } + .city-details { position: relative; padding: 1rem 1px 2rem; @@ -482,27 +647,33 @@ nav.filter-result.active { top: -13px; border: solid 1px #6d251c; } + .flex { display: flex; justify-content: space-around; flex-wrap: wrap; } -.city-details.flex > div { + +.city-details.flex>div { margin: 1rem; } + .service-in-town { padding: 0 1rem; } + h1 { font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem; } + h2 { font-weight: bold; margin: 1rem; font-size: 1.3rem; } + h3 { font-weight: bold; margin: 1rem; @@ -512,32 +683,38 @@ h3 { #travelling { padding: 2rem; } + #travelling-actions { display: flex; justify-content: center; gap: 1rem; margin-bottom: 1rem; } + .travel-distance { margin-bottom: 1rem; } -#explore .shop-inventory-listing { +#explore .shop-inventory-listing { margin: 2rem auto 1rem; width: 90%; } + .location-name { display: flex; align-items: center; justify-content: center; } + .location-name span { color: #846945; text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.7); margin: 0 1rem; } -.location-name:before, .location-name:after { + +.location-name:before, +.location-name:after { background-color: #846945; height: 2px; flex: 1; @@ -545,36 +722,45 @@ h3 { width: 4rem; drop-shadow: 1px 1px 0px rgba(255, 255, 255, 0.7); } + .shop-inventory-listing .listing { background-color: #fff; } + .store-list { display: flex; text-align: left; padding: 0.5rem; } + .store-list:nth-child(even) { background-color: #f2f0ec; } + .store-list .details { padding: 0 0.4rem; line-height: 1rem; flex-grow: 2; } + .store-list .name { font-weight: bold; } + .requirements { margin-top: 0.5rem; line-height: 1.3rem; } + .requirement-title { font-weight: bold; text-transform: capitalize; } + .store-cost { margin-top: 0.5rem; } + .store-icon { width: 64px; height: calc(64px + 27px); @@ -584,27 +770,33 @@ h3 { position: relative; margin-right: 0.5rem; } + .inventory-icon { width: 64px; - height: 64px;; + height: 64px; + ; padding: 0; background-repeat: no-repeat; background-size: contain; position: relative; margin-right: 0.5rem; } + .store-actions { width: 100%; position: absolute; bottom: 0; } + .store-actions button { width: 100%; padding: 0.3rem 0.5rem; } + .inventory-actions { width: 74px; } + .inventory-actions button { width: 100%; padding: 0.3rem 0.5rem; @@ -616,10 +808,12 @@ h3 { justify-content: space-between; gap: 1rem; } + #character-equipment-placement { border-spacing: 0; width: 192px; } + #character-equipment-placement td { display: table-cell; min-width: 64px; @@ -637,30 +831,37 @@ h3 { overflow: hidden; background-size: contain; } + #extra-inventory-info { margin-top: 1rem; } + .filter-container .listing { max-height: 400px; width: 100%; overflow: auto; } + @media(max-width: 650px) { #time-of-day { padding: 0 1rem; } + #inventory-page { flex-direction: column; } + #character-summary { width: 100%; display: flex; justify-content: space-between; align-items: flex-start; } + #extra-inventory-info { margin-top: 0rem; } + #inventory-section { margin-left: 0; margin-top: 2rem; @@ -670,29 +871,35 @@ h3 { #skill-list { width: 100%; } + #skill-list .skill-level { font-size: 2rem; vertical-align: middle; text-align: center; border: solid 1px #000; } + #skill-list .skill-details table { width: 100%; } + #skill-list .skill-title { text-align: left; padding: 0.6rem 0.6rem 0 0.6rem; line-height: 1.2rem; font-weight: bold; } + #skill-list .skill-description { - padding: 0 0.6rem 0.6rem; + padding: 0 0.6rem 0.6rem; line-height: 1.2rem; } + #skill-list .skill-exp { text-align: right; padding-right: 0.6rem; } + #skill-list tr:nth-child(even) .skill-details { background-color: #c7b7a1; } @@ -701,14 +908,17 @@ h3 { #chat { border: solid 1px #6d251c; } + #chat-messages { max-height: 250px; overflow: auto; } + .chat-message { line-height: 1.2rem; padding: 0.2rem 0.3rem 0.3rem; } + .chat-message:nth-child(even) { background: linear-gradient(270deg, rgba(0, 0, 0, 0) 0, rgba(196, 177, 149, 0.8) 100%); } @@ -716,12 +926,15 @@ h3 { .chat-message .from { font-weight: bold; } + .chat-message .from::after { content: ':'; } + #chat-form { display: flex; } + #chat-form input { flex-grow: 8; padding: 0.3rem; @@ -729,14 +942,17 @@ h3 { border-width: 1px 0 0; background: transparent; } + #chat-form input:focus { outline: none; } + #chat-form button { border-right-width: 0px; border-bottom-width: 0px; font-weight: bold; } + #chat-form button:active { top: 0; } @@ -747,10 +963,12 @@ h3 { margin-top: 1rem; border: 0; } + #game-footer nav { margin: 0; padding: 0; } + #game-footer img { width: 1rem; height: 1rem; @@ -768,6 +986,7 @@ footer { display: flex; justify-content: center; } + .tooltip[title]:focus::after { content: attr(title); background-color: #fff; @@ -790,4 +1009,4 @@ footer { .dungeon-room-description { padding: 1rem; -} +} \ No newline at end of file diff --git a/src/server/inventory.ts b/src/server/inventory.ts index 3074644..1ef7236 100644 --- a/src/server/inventory.ts +++ b/src/server/inventory.ts @@ -16,6 +16,8 @@ export async function addInventoryItem(playerId: string, item: ShopEquipment) { count: item.count, profession: item.profession, icon: item.icon, + current_exp: 0, + current_level: 1, requirements: { level: item.requirements.level, strength: item.requirements.strength, diff --git a/src/server/views/inventory.ts b/src/server/views/inventory.ts index d9b8c43..f3ffb89 100644 --- a/src/server/views/inventory.ts +++ b/src/server/views/inventory.ts @@ -5,6 +5,8 @@ import { capitalize } from "lodash"; import { ProgressBar } from "./components/progress-bar"; import { Player } from "../../shared/player"; import { renderStatBoostWithPlayerIncrease } from "./components/stats"; +import { getDurabilityApproximation, expToLevel} from "../../shared/inventory"; +import { slugify } from "../../shared/utils"; function icon(icon_name?: string): string { const placeholder = 'https://placehold.co/64x64/af936c/6d5f4d'; @@ -18,35 +20,38 @@ function renderEquipmentPlacementGrid(items: EquippedItemDetails[]) { const map: Record = items.filter(item => item.is_equipped).reduce((acc, item) => { acc[item.equipment_slot] = item; return acc; - }, {}); + }, {}); + + const leftHand = map.LEFT_HAND ? map.LEFT_HAND : (map.TWO_HANDED ? map.TWO_HANDED : null); + const rightHand = map.RIGHT_HAND ? map.RIGHT_HAND : (map.TWO_HANDED ? map.TWO_HANDED : null); const html = ` - - - - - -
+ ${map.HEAD ? (map.HEAD.icon ? '' : map.HEAD.name) : 'HEAD'} + ${map.ARMS ? (map.ARMS.icon ? '' : map.ARMS.name) : 'ARMS'}
+ ${map.LEFT_HAND ? (map.LEFT_HAND.icon ? '' : map.LEFT_HAND.name) : (map.TWO_HANDED ? (map.TWO_HANDED.icon ? '' : map.TWO_HANDED.name) : 'L_HAND')} + ${map.CHEST ? (map.CHEST.icon ? '' : map.CHEST.name) : 'CHEST'} + ${map.RIGHT_HAND ? (map.RIGHT_HAND.icon ? '' : map.RIGHT_HAND.name) : (map.TWO_HANDED ? (map.TWO_HANDED.icon ? '' : map.TWO_HANDED.name) : 'R_HAND')}
+ ${map.LEGS ? (map.LEGS.icon ? '' : map.LEGS.name) : 'LEGS'} @@ -87,7 +92,7 @@ function renderStatBoost(name: string, val: number | string): string { function renderInventoryItem(player: Player, item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string { return `
-
+
${item.name}
@@ -107,12 +112,6 @@ function renderInventoryItem(player: Player, item: EquippedItemDetails , action: ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''} ${item.boosts.damage ? renderStatBoostWithPlayerIncrease(player, item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item) : ''} ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''} - ${ProgressBar(item.currentAp, item.maxAp, `dur-${item.item_id}`, { - startingColor: '#7be67b', - endingColor: '#7be67b', - displayPercent: false, - title: item.type === 'SPELL' ? 'Uses' : 'Durability' - })}
${item.hasOwnProperty('id') ? `
${item.cost.toLocaleString()}G
` : ''}
diff --git a/src/shared/equipped.ts b/src/shared/equipped.ts index 2dc33a7..3e87998 100644 --- a/src/shared/equipped.ts +++ b/src/shared/equipped.ts @@ -1,12 +1,5 @@ import {EquipmentSlot, InventoryItem, InventoryType} from "./inventory"; -export type EquippedItem = { - item_id: number; - player_id: string; - type: InventoryType; - equipment_slot: EquipmentSlot -} - export type EquippedItemDetails = InventoryItem & { is_equipped: boolean }; diff --git a/src/shared/inventory.ts b/src/shared/inventory.ts index f78d65b..d7eb879 100644 --- a/src/shared/inventory.ts +++ b/src/shared/inventory.ts @@ -2,6 +2,8 @@ import {Profession} from "./profession"; import {SkillID} from "./skills"; import { max } from 'lodash'; +export type DurabilityApproximation = 'Perfect' | 'Great' | 'Good' | 'Fair' | 'Poor' | 'Terrible' | 'About to Break'; + export type InventoryType = 'ARMOUR' | 'WEAPON' | 'SPELL'; export type ArmourEquipmentSlot = 'HEAD' | 'LEGS' | 'ARMS' | 'CHEST'; @@ -29,6 +31,8 @@ export type InventoryItem = { cost: number; count: number; icon: string; + current_exp: number; + current_level: number; requirements: { level: number, strength: number, @@ -64,3 +68,41 @@ export function repairCost(item: InventoryItem): number { return max([Math.floor(totalCost * damageRatio), 1]); } + + +export function expToLevel(level: number): number { + if (level <= 1) return 0; + if (level <= 5) { + // Linear growth for levels 1-5 + return 100 * (level - 1); + } else { + // Exponential growth starting from level 5 + const baseExp = 400; // Exp required for level 5 + const growthFactor = 1.2; + return Math.floor(baseExp * Math.pow(growthFactor, level - 5)); + } + +} + +export function getDurabilityApproximation(item?: InventoryItem): DurabilityApproximation | "" { + if (!item) { + return ""; + } + + const durability = item.currentAp / item.maxAp; + if (durability === 1) { + return "Perfect"; + } else if (durability >= 0.90) { + return "Great"; + } else if (durability >= 0.75) { + return "Good"; + } else if (durability >= 0.50) { + return "Fair"; + } else if (durability >= 0.25) { + return "Poor"; + } else if (durability >= 0.10) { + return "Terrible"; + } else { + return "About to Break"; + } +} diff --git a/src/shared/utils.ts b/src/shared/utils.ts new file mode 100644 index 0000000..29b3f11 --- /dev/null +++ b/src/shared/utils.ts @@ -0,0 +1,9 @@ +export function slugify(str: string): string { + return str + .toLowerCase() + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-'); + +} \ No newline at end of file -- 2.25.1