2025-11-26 06:53:44 +00:00
|
|
|
<template>
|
|
|
|
|
<div class="flex flex-col h-full bg-black text-[#99e550] relative">
|
|
|
|
|
<!-- Content -->
|
|
|
|
|
<div class="flex-grow overflow-y-auto p-4 custom-scrollbar flex flex-col gap-4">
|
|
|
|
|
<div
|
|
|
|
|
v-for="loc in locations"
|
|
|
|
|
:key="loc.id"
|
|
|
|
|
class="border-2 p-4 relative transition-all"
|
|
|
|
|
:class="isLocked(loc) ? 'border-gray-600 opacity-70' : 'border-[#99e550] hover:bg-[#0f2a0f]'"
|
|
|
|
|
>
|
|
|
|
|
<!-- Title -->
|
|
|
|
|
<div class="text-xl font-bold tracking-widest mb-2 text-[#99e550]">
|
|
|
|
|
{{ loc.name }}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Description -->
|
2025-11-27 09:31:56 +00:00
|
|
|
<p class="text-xs text-white mb-4">
|
2025-11-26 06:53:44 +00:00
|
|
|
{{ loc.description }}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<!-- Costs & Reqs -->
|
|
|
|
|
<div class="flex flex-wrap gap-4 mb-4 text-sm font-mono">
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-[#99e550]">消耗:</span>
|
|
|
|
|
<div class="flex items-center gap-1" :class="canAffordHunger(loc) ? 'text-[#9fd75b]' : 'text-red-500'">
|
|
|
|
|
<Drumstick :size="14" /> {{ loc.costHunger }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-1" :class="canAffordGold(loc) ? 'text-[#f6b26b]' : 'text-red-500'">
|
|
|
|
|
<Coins :size="14" /> {{ loc.costGold }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="loc.reqStats" class="flex items-center gap-2">
|
|
|
|
|
<span class="text-[#99e550]">要求:</span>
|
|
|
|
|
<span v-if="loc.reqStats.str" :class="meetsStr(loc) ? 'text-[#9fd75b]' : 'text-red-500'">STR {{ loc.reqStats.str }}</span>
|
|
|
|
|
<span v-if="loc.reqStats.int" :class="meetsInt(loc) ? 'text-[#9fd75b]' : 'text-red-500'">INT {{ loc.reqStats.int }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-11-27 09:31:56 +00:00
|
|
|
<!-- Action Buttons -->
|
|
|
|
|
<div class="flex gap-2 mt-auto">
|
|
|
|
|
<!-- Battle Button: Requires Stats & Costs -->
|
|
|
|
|
<button
|
|
|
|
|
@click="!isLocked(loc) && canAfford(loc) && $emit('selectLocation', { location: loc, mode: 'battle' })"
|
|
|
|
|
:disabled="!canAfford(loc) || isLocked(loc)"
|
|
|
|
|
class="flex-1 py-2 text-sm tracking-[0.1em] border transition-colors"
|
|
|
|
|
:class="(!canAfford(loc) || isLocked(loc))
|
|
|
|
|
? 'border-gray-600 text-gray-500 cursor-not-allowed'
|
|
|
|
|
: 'border-[#d95763] text-[#d95763] hover:bg-[#d95763] hover:text-black'"
|
|
|
|
|
>
|
|
|
|
|
<Swords :size="16" class="inline mr-1" /> 戰鬥
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<!-- View Button: No Restrictions -->
|
|
|
|
|
<button
|
|
|
|
|
@click="$emit('selectLocation', { location: loc, mode: 'view' })"
|
|
|
|
|
class="flex-1 py-2 text-sm tracking-[0.1em] border transition-colors border-[#2ce8f4] text-[#2ce8f4] hover:bg-[#2ce8f4] hover:text-black"
|
|
|
|
|
>
|
|
|
|
|
<Eye :size="16" class="inline mr-1" /> 參觀
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
<!-- Side Decoration Bar -->
|
|
|
|
|
<div class="absolute top-2 bottom-2 right-2 w-2" :class="isLocked(loc) ? 'bg-gray-600' : 'bg-[#99e550]'"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-11-27 09:31:56 +00:00
|
|
|
import { Map, Drumstick, Coins, X, Swords, Eye } from 'lucide-vue-next';
|
2025-11-26 09:53:03 +00:00
|
|
|
import PixelFrame from './PixelFrame.vue';
|
|
|
|
|
import PixelButton from './PixelButton.vue';
|
|
|
|
|
|
|
|
|
|
type AdventureLocation = any;
|
|
|
|
|
type EntityStats = any;
|
2025-11-26 06:53:44 +00:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
locations: AdventureLocation[];
|
|
|
|
|
playerStats: EntityStats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
|
defineEmits(['selectLocation', 'close']);
|
|
|
|
|
|
|
|
|
|
const canAffordHunger = (loc: AdventureLocation) => (props.playerStats.hunger || 0) >= loc.costHunger;
|
|
|
|
|
const canAffordGold = (loc: AdventureLocation) => (props.playerStats.gold || 0) >= loc.costGold;
|
|
|
|
|
const meetsStr = (loc: AdventureLocation) => !loc.reqStats?.str || (props.playerStats.str || 0) >= loc.reqStats.str;
|
|
|
|
|
const meetsInt = (loc: AdventureLocation) => !loc.reqStats?.int || (props.playerStats.int || 0) >= loc.reqStats.int;
|
|
|
|
|
|
|
|
|
|
const isLocked = (loc: AdventureLocation) => !meetsStr(loc) || !meetsInt(loc);
|
|
|
|
|
const canAfford = (loc: AdventureLocation) => canAffordHunger(loc) && canAffordGold(loc);
|
|
|
|
|
</script>
|