1
0
forked from noxious/client

Bug fixes and improvements to character select screen

This commit is contained in:
Dennis Postma 2024-11-25 00:40:21 +01:00
parent 25a2fd24f3
commit 924d5bdd13
3 changed files with 44 additions and 46 deletions

View File

@ -20,19 +20,19 @@
v-for="character in characters" v-for="character in characters"
:key="character.id" :key="character.id"
class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')] after:absolute after:w-full after:h-px after:bg-gray-500" class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')] after:absolute after:w-full after:h-px after:bg-gray-500"
:class="{ active: selected_character == character.id }" :class="{ active: selectedCharacterId == character.id }"
> >
<img src="/assets/avatar/default/head.png" class="w-9 h-9 object-contain absolute top-1/2 -translate-y-1/2" alt="Player head" /> <img src="/assets/avatar/default/head.png" class="w-9 h-9 object-contain absolute top-1/2 -translate-y-1/2" alt="Player head" />
<input class="h-full w-full absolute m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0" type="radio" name="character" :value="character.id" v-model="selected_character" /> <input class="h-full w-full absolute m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0" type="radio" name="character" :value="character.id" v-model="selectedCharacterId" />
</div> </div>
<div class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')]" :class="{ active: characters.length == 0 }" v-if="characters.length < 4"> <div class="character relative rounded-l border border-solid border-gray-500 w-9 h-[50px] bg-[url('/assets/ui-texture.png')]" :class="{ active: characters.length == 0 }" v-if="characters.length < 4">
<button class="p-0 h-full w-full flex flex-col justify-between focus-visible:outline-offset-0" @click="isModalOpen = true"> <button class="p-0 h-full w-full flex flex-col justify-between focus-visible:outline-offset-0" @click="isCreateNewCharacterModalOpen = true">
<img class="w-6 h-6 object-contain absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2" draggable="false" src="/assets/icons/plus-icon.svg" /> <img class="w-6 h-6 object-contain absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2" draggable="false" src="/assets/icons/plus-icon.svg" />
</button> </button>
</div> </div>
</div> </div>
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-6 justify-center" v-if="selected_character"> <div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-6 justify-center" v-if="selectedCharacterId">
<input class="input-field w-[158px]" type="text" name="name" :placeholder="characters.find((c) => c.id == selected_character)?.name" /> <input class="input-field w-[158px]" type="text" name="name" :placeholder="characters.find((c) => c.id == selectedCharacterId)?.name" />
<div class="flex flex-col gap-4 items-center"> <div class="flex flex-col gap-4 items-center">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="bg-[url('/assets/ui-elements/character-select-ui-shape.svg')] w-[190px] h-52 bg-no-repeat bg-center flex items-center justify-between"> <div class="bg-[url('/assets/ui-elements/character-select-ui-shape.svg')] w-[190px] h-52 bg-no-repeat bg-center flex items-center justify-between">
@ -51,11 +51,11 @@
</div> </div>
<!-- TODO: update gender on (selected) character --> <!-- TODO: update gender on (selected) character -->
<div class="flex justify-between w-[190px]"> <div class="flex justify-between w-[190px]">
<button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selected_character)?.characterType?.gender === 'MALE' }"> <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'MALE' }">
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" /> <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
<span class="text-white">Male</span> <span class="text-white">Male</span>
</button> </button>
<button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selected_character)?.characterType?.gender === 'FEMALE' }"> <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'FEMALE' }">
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" /> <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
<span class="text-white">Female</span> <span class="text-white">Female</span>
</button> </button>
@ -64,30 +64,23 @@
</div> </div>
</div> </div>
<div class="w-2/3 h-full bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center rounded-r-md"> <div class="w-2/3 h-full bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center rounded-r-md">
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-10" v-if="selected_character"> <div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-10" v-if="selectedCharacterId">
<div class="flex flex-col gap-3 w-full"> <div class="flex flex-col gap-3 w-full">
<span class="text-sm">Hairstyle</span> <span class="text-sm">Hairstyle</span>
<div class="flex gap-2 flex-wrap max-h-20 overflow-y-auto scrollbar"> <div class="flex gap-2 flex-wrap max-h-20 overflow-y-auto scrollbar">
<div <div
class="hair-deselect relative flex justify-center items-center bg-gray border border-solid border-gray-500 w-[18px] h-[18px] p-2 rounded-sm class="hair-deselect relative flex justify-center items-center bg-gray border border-solid border-gray-500 w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-white focus-visible:bg-cyan has-[:checked]:bg-cyan has-[:checked]:border-transparent"
hover:bg-gray-500 hover:border-gray-400
focus-visible:outline-none focus-visible:border-white focus-visible:bg-cyan
has-[:checked]:bg-cyan has-[:checked]:border-transparent"
> >
<img src="/assets/icons/x-button-gray.svg" class="w-4 h-4" alt="Empty button" /> <img src="/assets/icons/x-button-gray.svg" class="w-4 h-4" alt="Empty button" />
<input type="radio" name="hair" :value="null" v-model="selectedHair" :checked="characters.find((c) => c.id == selected_character)?.characterHairId === null" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" /> <input type="radio" name="hair" :value="null" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
</div> </div>
<!-- TODO #255: make radio button so we can set a value, do the same with swatches --> <!-- TODO #255: make radio button so we can set a value, do the same with swatches -->
<div <div
v-for="hair in characterHairs" v-for="hair in characterHairs"
class="relative flex justify-center items-center bg-gray border border-solid border-gray-500 w-[18px] h-[18px] p-2 rounded-sm class="relative flex justify-center items-center bg-gray border border-solid border-gray-500 w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
hover:bg-gray-500 hover:border-gray-400
focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500
has-[:checked]:bg-cyan has-[:checked]:border-transparent"
> >
<img class="w-4 h-4" :src="config.server_endpoint + '/assets/sprites/' + hair.spriteId + '/front.png'" alt="Hair sprite" /> <img class="w-4 h-4" :src="config.server_endpoint + '/assets/sprites/' + hair.spriteId + '/front.png'" alt="Hair sprite" />
<input type="radio" name="hair" :value="hair" v-model="selectedHair" :checked="characters.find((c) => c.id == selected_character)?.characterHairId == hair.id" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" /> <input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
</div> </div>
</div> </div>
</div> </div>
@ -108,26 +101,26 @@
<div class="button-wrapper flex self-center justify-end gap-4 max-w-[860px] w-full" v-if="!isLoading"> <div class="button-wrapper flex self-center justify-end gap-4 max-w-[860px] w-full" v-if="!isLoading">
<button class="btn-empty min-w-48" @click.stop="gameStore.disconnectSocket()">Back</button> <button class="btn-empty min-w-48" @click.stop="gameStore.disconnectSocket()">Back</button>
<button class="btn-cyan min-w-48 disabled:bg-cyan-800 disabled:cursor-not-allowed" :disabled="!selected_character" @click="select_character()">Play now</button> <button class="btn-cyan min-w-48 disabled:bg-cyan-800 disabled:cursor-not-allowed" :disabled="!selectedCharacterId" @click="loginWithCharacter()">Play now</button>
</div> </div>
</div> </div>
</div> </div>
<!-- CREATE CHARACTER MODAL --> <!-- CREATE CHARACTER MODAL -->
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275"> <Modal :isModalOpen="isCreateNewCharacterModalOpen" @modal:close="isCreateNewCharacterModalOpen = false" :modal-width="430" :modal-height="275">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium text-white">Create your character</h3> <h3 class="m-0 font-medium text-white">Create your character</h3>
</template> </template>
<template #modalBody> <template #modalBody>
<div class="p-4 h-[calc(100%_-_32px)]"> <div class="p-4 h-[calc(100%_-_32px)]">
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between"> <form method="post" @submit.prevent="createCharacter" class="h-full flex flex-col justify-between">
<div class="form-field-full"> <div class="form-field-full">
<label for="name" class="text-white">Nickname</label> <label for="name" class="text-white">Nickname</label>
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname..." /> <input class="input-field" v-model="newCharacterName" name="name" id="name" placeholder="Enter a nickname..." />
</div> </div>
<div class="grid grid-flow-col justify-stretch gap-4"> <div class="grid grid-flow-col justify-stretch gap-4">
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button> <button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isCreateNewCharacterModalOpen = false">Cancel</button>
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button> <button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
</div> </div>
</form> </form>
@ -136,7 +129,7 @@
</Modal> </Modal>
<!-- DELETE CHARACTER MODAL --> <!-- DELETE CHARACTER MODAL -->
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete"> <ConfirmationModal v-if="deletingCharacter != null" :confirm-function="deleteCharacter.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium text-white">Delete character?</h3> <h3 class="m-0 font-medium text-white">Delete character?</h3>
</template> </template>
@ -152,7 +145,7 @@
<script setup lang="ts"> <script setup lang="ts">
import config from '@/config' import config from '@/config'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { onBeforeUnmount, ref } from 'vue' import { onBeforeUnmount, ref, watch } from 'vue'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { type Character as CharacterT, type CharacterHair } from '@/types' import { type Character as CharacterT, type CharacterHair } from '@/types'
import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue' import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue'
@ -161,11 +154,17 @@ const gameStore = useGameStore()
const isLoading = ref(true) const isLoading = ref(true)
const characters = ref([] as CharacterT[]) const characters = ref([] as CharacterT[])
const deletingCharacter = ref(null as CharacterT | null) const deletingCharacter = ref(null as CharacterT | null)
const selectedCharacterId = ref<number | null>(null)
const characterHairs = ref([] as CharacterHair[]) const isCreateNewCharacterModalOpen = ref<boolean>(false)
const selectedHair = ref(null as CharacterHair | null) const newCharacterName = ref<string>('')
const characterHairs = ref<CharacterHair[]>([])
const selectedHairId = ref<number | null>(null)
// Fetch characters // Fetch characters
setTimeout(() => {
gameStore.connection?.emit('character:list')
}, 750)
gameStore.connection?.on('character:list', (data: any) => { gameStore.connection?.on('character:list', (data: any) => {
characters.value = data characters.value = data
isLoading.value = false isLoading.value = false
@ -177,40 +176,39 @@ gameStore.connection?.on('character:list', (data: any) => {
}) })
}) })
setTimeout(() => {
gameStore.connection?.emit('character:list')
}, 750)
// Select character logics // Select character logics
const selected_character = ref(null) function loginWithCharacter() {
function select_character() { if (!selectedCharacterId.value) return
if (!selected_character.value) return
deletingCharacter.value = null deletingCharacter.value = null
gameStore.connection?.emit('character:connect', { gameStore.connection?.emit('character:connect', {
characterId: selected_character.value, characterId: selectedCharacterId.value,
characterHairId: selectedHair.value?.id characterHairId: selectedHairId.value
}) })
gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data)) gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data))
} }
// Delete character logics // Delete character logics
function delete_character(characterId: number) { function deleteCharacter(characterId: number) {
if (!characterId) return if (!characterId) return
deletingCharacter.value = null deletingCharacter.value = null
gameStore.connection?.emit('character:delete', { characterId: characterId }) gameStore.connection?.emit('character:delete', { characterId: characterId })
} }
// Create character logics // Create character logics
const isModalOpen = ref(false) function createCharacter() {
const name = ref('')
function create() {
gameStore.connection?.on('character:create:success', (data: CharacterT) => { gameStore.connection?.on('character:create:success', (data: CharacterT) => {
gameStore.setCharacter(data) gameStore.setCharacter(data)
isModalOpen.value = false isCreateNewCharacterModalOpen.value = false
}) })
gameStore.connection?.emit('character:create', { name: name.value }) gameStore.connection?.emit('character:create', { name: newCharacterName.value })
} }
// Watch changes for selected character and update hairs
watch(selectedCharacterId, (characterId) => {
if (!characterId) return
selectedHairId.value = characters.value.find((c) => c.id == characterId)?.characterHairId ?? null
})
onBeforeUnmount(() => { onBeforeUnmount(() => {
gameStore.connection?.off('character:list') gameStore.connection?.off('character:list')
gameStore.connection?.off('character:connect') gameStore.connection?.off('character:connect')