Files
shoot-miniprograms/src/pages/battle-room.vue
2025-06-22 01:41:51 +08:00

463 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import PlayerSeats from "@/components/PlayerSeats.vue";
import Guide from "@/components/Guide.vue";
import Timer from "@/components/Timer.vue";
import SButton from "@/components/SButton.vue";
import BowTarget from "@/components/BowTarget.vue";
import BattleHeader from "@/components/BattleHeader.vue";
import BattleFooter from "@/components/BattleFooter.vue";
import BowPower from "@/components/BowPower.vue";
import ShootProgress from "@/components/ShootProgress.vue";
import PlayersRow from "@/components/PlayersRow.vue";
import SModal from "@/components/SModal.vue";
import { getRoomAPI, destroyRoomAPI, exitRoomAPI, startRoomAPI } from "@/apis";
import { MESSAGETYPES, roundsName } from "@/constants";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
const step = ref(1);
const seq = ref(0);
const timerSeq = ref(0);
const battleId = ref("");
const room = ref({});
const roomNumber = ref("");
const owner = ref({});
const opponent = ref({});
const redTeam = ref([]);
const blueTeam = ref([]);
const currentShooterId = ref(0);
const players = ref([]);
const currentRound = ref(1);
const totalRounds = ref(0);
const start = ref(false);
const power = ref(0);
const scores = ref([]);
const tips = ref("即将开始...");
const roundResults = ref([]);
const redPoints = ref(0);
const bluePoints = ref(0);
const playersScores = ref({});
const showModal = ref(false);
onLoad(async (options) => {
if (options.roomNumber) {
roomNumber.value = options.roomNumber;
const result = await getRoomAPI(options.roomNumber);
room.value = result;
result.members.some((m) => {
if (m.userInfo.id === result.creator) {
owner.value = {
id: m.userInfo.id,
name: m.userInfo.name,
avatar: m.userInfo.avatar,
};
return true;
}
return false;
});
if (result.battleType === 1) {
if (user.value.id !== owner.value.id) {
opponent.value = {
id: user.value.id,
name: user.value.nickName,
avatar: user.value.avatar,
};
} else if (result.members.length > 1) {
result.members.some((m) => {
if (m.userInfo.id !== owner.value.id) {
opponent.value = {
id: m.userInfo.id,
name: m.userInfo.name,
avatar: m.userInfo.avatar,
};
return true;
}
return false;
});
}
} else if (result.battleType === 2) {
result.members.forEach((m) => {
players.value.push(m.userInfo);
});
}
}
});
const startGame = async () => {
const result = await startRoomAPI(room.value.number);
timerSeq.value += 1;
step.value = 2;
};
async function onReceiveMessage(messages = []) {
messages.forEach((msg) => {
if (
msg.roomNumber === roomNumber.value ||
(battleId.value && msg.id === battleId.value) ||
msg.constructor === MESSAGETYPES.WaitForAllReady
) {
console.log("收到消息:", msg);
}
if (!start.value && msg.constructor === MESSAGETYPES.ShootSyncMeArrowID) {
scores.value.push(msg.target);
power.value = msg.target.battery;
}
if (msg.constructor === MESSAGETYPES.WaitForAllReady) {
// 这里会掉多次;
timerSeq.value += 1;
battleId.value = msg.id;
if (room.value.battleType === 1) {
redTeam.value = msg.groupUserStatus.redTeam;
blueTeam.value = msg.groupUserStatus.blueTeam;
}
step.value = 2;
}
if (msg.roomNumber === roomNumber.value) {
if (msg.constructor === MESSAGETYPES.UserEnterRoom) {
if (room.value.battleType === 1) {
opponent.value = {
id: msg.userId,
name: msg.name,
avatar: msg.avatar,
};
}
if (room.value.battleType === 2) {
players.value.push({
id: msg.userId,
name: msg.name,
avatar: msg.avatar,
});
}
}
if (!start.value && msg.constructor === MESSAGETYPES.UserExitRoom) {
if (room.value.battleType === 1) {
opponent.value = {
id: "",
};
}
if (room.value.battleType === 2) {
players.value = players.value.filter((p) => p.id !== msg.userId);
}
}
if (msg.constructor === MESSAGETYPES.RoomDestroy) {
uni.showToast({
title: "房间已解散",
icon: "none",
});
roomNumber.value = "";
setTimeout(() => {
uni.navigateBack();
}, 1000);
}
}
if (msg.id === battleId.value) {
if (msg.constructor === MESSAGETYPES.AllReady) {
start.value = true;
timerSeq.value = 0;
scores.value = [];
totalRounds.value = msg.groupUserStatus.config.maxRounds;
step.value = 3;
if (room.value.battleType === 2) {
tips.value = "请在90秒内射完12支箭";
seq.value += 1;
}
}
if (msg.constructor === MESSAGETYPES.ToSomeoneShoot) {
if (room.value.battleType === 1) {
scores.value = [];
seq.value += 1;
currentShooterId.value = msg.userId;
if (owner.id !== currentShooterId.value) {
tips.value = `请红队射箭-第${roundsName[currentRound.value]}`;
} else {
tips.value = `请蓝队射箭-第${roundsName[currentRound.value]}`;
}
}
}
if (msg.constructor === MESSAGETYPES.ShootResult) {
if (room.value.battleType === 1) {
scores.value = [msg.target];
}
if (room.value.battleType === 2) {
scores.value.push(msg.target);
power.value = msg.target.battery;
}
playersScores.value[msg.userId].push(msg.target);
}
if (msg.constructor === MESSAGETYPES.RoundPoint) {
bluePoints.value += msg.blueScore;
redPoints.value += msg.redScore;
}
if (msg.constructor === MESSAGETYPES.CurrentRoundEnded) {
const result = msg.preRoundResult;
scores.value = [];
currentShooterId.value = 0;
if (
result.currentRound > 0 &&
result.currentRound < totalRounds.value
) {
// 开始下一轮;
roundResults.value = result.roundResults;
currentRound.value = result.currentRound + 1;
}
}
if (msg.constructor === MESSAGETYPES.MatchOver) {
uni.redirectTo({
url: `/pages/battle-result?battleId=${msg.id}&winner=${msg.endStatus.winner}`,
});
}
}
});
}
const onLeaveRoom = () => {
if (owner.value.id === user.value.id) {
showModal.value = true;
} else {
uni.navigateBack();
}
};
const destroyRoom = async () => {
await destroyRoomAPI(roomNumber.value);
};
const exitRoom = async () => {
uni.navigateBack();
};
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
});
onUnmounted(() => {
uni.$off("socket-inbox", onReceiveMessage);
if (owner.value.id !== user.value.id) {
exitRoomAPI(roomNumber.value);
}
});
</script>
<template>
<Container title="对战" :onBack="onLeaveRoom">
<view class="standby-phase" v-if="step === 1">
<Guide>
<view :style="{ display: 'flex', flexDirection: 'column' }">
<text :style="{ color: '#fed847' }">人都到齐了吗</text>
<text>天赋异禀的弓箭手们比赛即将开始</text>
</view>
</Guide>
<view v-if="room.battleType === 1" class="team-mode">
<image src="../static/1v1-bg.png" mode="widthFix" />
<view>
<view class="player" :style="{ transform: 'translateY(-60px)' }">
<image :src="owner.avatar" mode="widthFix" />
<text>{{ owner.name }}</text>
</view>
<image src="../static/versus.png" mode="widthFix" />
<block v-if="opponent.id">
<view class="player" :style="{ transform: 'translateY(60px)' }">
<image :src="opponent.avatar" mode="widthFix" />
<text v-if="opponent.name">{{ opponent.name }}</text>
</view>
</block>
<block v-else>
<view class="no-player">
<image src="../static/question-mark.png" mode="widthFix" />
</view>
</block>
</view>
</view>
<PlayerSeats
v-if="room.battleType === 2"
:total="room.count || 10"
:players="players"
/>
<view>
<SButton
v-if="user.id === owner.id && room.battleType === 1"
:disabled="!opponent.id"
:onClick="startGame"
>进入对战</SButton
>
<SButton
v-if="user.id === owner.id && room.battleType === 2"
:disabled="players.length < 2"
:onClick="startGame"
>进入大乱斗</SButton
>
<SButton v-if="user.id !== owner.id" disabled>等待房主开启对战</SButton>
<text class="tips">创建者点击下一步所有人即可进入游戏</text>
</view>
</view>
<view v-if="step === 2" :style="{ width: '100%' }">
<BattleHeader
:blueTeam="blueTeam"
:redTeam="redTeam"
:players="players"
/>
<Guide noBg>
<view :style="{ display: 'flex', justifyContent: 'space-between' }">
<view :style="{ display: 'flex', flexDirection: 'column' }">
<text :style="{ color: '#fed847' }">请预先射几箭测试</text>
<text>请确保射击距离只有5米</text>
</view>
<BowPower :power="power" />
</view>
</Guide>
<BowTarget
:scores="scores"
:tips="
!start && scores.length > 0
? `本次射程${scores[scores.length - 1].dst / 100}米,${
scores[scores.length - 1].dst / 100 >= 5 ? '已' : '未'
}达到距离要求`
: ''
"
/>
</view>
<view v-if="step === 3">
<ShootProgress :tips="tips" :seq="seq" :start="start" />
<PlayersRow
v-if="room.battleType === 1"
:blueTeam="blueTeam"
:redTeam="redTeam"
:currentShooterId="currentShooterId"
/>
<BowTarget
:power="power"
:currentRound="currentRound"
:totalRound="totalRounds"
:scores="scores"
/>
<BattleFooter
v-if="room.battleType === 1"
:roundResults="roundResults"
:redPoints="redPoints"
:bluePoints="bluePoints"
/>
<PlayerScore
v-if="room.battleType === 2"
v-for="(player, index) in players"
:key="index"
:name="player.name"
:avatar="player.avatar"
:scores="playersScores[player.id]"
:done="playersScores[player.id].length === 12"
/>
</view>
<Timer :seq="timerSeq" />
<SModal :show="showModal" :onClose="() => (showModal = false)">
<view class="btns">
<button @click="exitRoom">暂时离开</button>
<button @click="destroyRoom">解散房间</button>
</view>
</SModal>
</Container>
</template>
<style scoped>
.standby-phase {
width: 100%;
height: calc(100% - 40px);
overflow-x: hidden;
}
.tips {
color: #fff9;
width: 100%;
text-align: center;
display: block;
margin-top: 10px;
font-size: 12px;
}
.player-unknow {
width: 40px;
height: 40px;
margin: 0 10px;
border: 1px solid #fff3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #69686866;
}
.player-unknow > image {
width: 40%;
}
.team-mode {
width: calc(100vw - 30px);
height: 120vw;
margin: 15px;
}
.team-mode > image:first-child {
position: absolute;
width: calc(100vw - 30px);
z-index: -1;
}
.team-mode > view {
display: flex;
justify-content: center;
align-items: center;
height: 95%;
}
.player {
width: 70px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transform: translateY(-60px);
color: #fff9;
font-size: 14px;
}
.player > image {
width: 70px;
height: 70px;
border-radius: 50%;
background-color: #ccc;
margin-bottom: 5px;
}
.player > text {
width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.team-mode > view > image:nth-child(2) {
width: 120px;
}
.no-player {
width: 70px;
height: 70px;
border-radius: 50%;
background-color: #ccc;
display: flex;
justify-content: center;
align-items: center;
transform: translateY(60px);
}
.no-player > image {
width: 20px;
margin-right: 2px;
}
.btns {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.btns > button {
color: #000;
background-color: #fed847;
padding: 10px;
width: 200px;
border-radius: 20px;
font-size: 14px;
margin: 10px;
}
</style>