Files
shoot-miniprograms/src/pages/battle-room.vue
2025-07-05 14:52:41 +08:00

533 lines
15 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 ScreenHint from "@/components/ScreenHint.vue";
import RoundEndTip from "@/components/RoundEndTip.vue";
import TestDistance from "@/components/TestDistance.vue";
import { getRoomAPI, destroyRoomAPI, exitRoomAPI, startRoomAPI } from "@/apis";
import { MESSAGETYPES, roundsName, getMessageTypeName } 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 startCount = ref(false);
const power = ref(0);
const scores = ref([]);
const blueScores = ref([]);
const tips = ref("即将开始...");
const roundResults = ref([]);
const redPoints = ref(0);
const bluePoints = ref(0);
const currentRedPoint = ref(0);
const currentBluePoint = ref(0);
const showRoundTip = ref(false);
const playersScores = ref({});
const showModal = ref(false);
const halfTimeTip = ref(false);
const total = ref(90);
onLoad(async (options) => {
if (options.battleId) {
const battleInfo = uni.getStorageSync(`battle-${options.battleId}`);
console.log("----battleInfo", battleInfo);
if (battleInfo) {
battleId.value = battleInfo.id;
start.value = true;
redTeam.value = battleInfo.redTeam;
blueTeam.value = battleInfo.blueTeam;
currentRound.value = battleInfo.currentRound;
bluePoints.value = battleInfo.blueScore;
redPoints.value = battleInfo.redScore;
totalRounds.value = battleInfo.maxRound;
roundResults.value = battleInfo.roundResults;
}
}
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) {
total.value = 15;
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("收到消息:", getMessageTypeName(msg.constructor), msg);
}
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) {
if (msg.userId === room.value.creator) {
owner.value = {
id: msg.userId,
name: msg.name,
avatar: msg.avatar,
};
} else {
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) {
if (msg.userId === room.value.creator) {
owner.value = {
id: "",
};
} else {
opponent.value = {
id: "",
};
}
}
if (room.value.battleType === 2) {
players.value = players.value.filter((p) => p.id !== msg.userId);
}
}
if (msg.constructor === MESSAGETYPES.RoomDestroy && !battleId.value) {
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.MeleeAllReady) {
start.value = true;
startCount.value = true;
seq.value += 1;
timerSeq.value = 0;
tips.value = "请连续射出6支箭";
scores.value = [];
}
if (msg.constructor === MESSAGETYPES.ToSomeoneShoot) {
if (room.value.battleType === 1) {
if (currentShooterId.value !== msg.userId) {
scores.value = [];
blueScores.value = [];
seq.value += 1;
currentShooterId.value = msg.userId;
if (redTeam.value[0].id === currentShooterId.value) {
tips.value = `请红队射箭-第${roundsName[currentRound.value]}`;
} else {
tips.value = `请蓝队射箭-第${roundsName[currentRound.value]}`;
}
}
}
}
if (msg.constructor === MESSAGETYPES.ShootResult) {
if (room.value.battleType === 1) {
const isRed = redTeam.value.find((item) => item.id === msg.userId);
if (isRed) {
scores.value = [msg.target];
} else {
blueScores.value = [msg.target];
}
}
if (room.value.battleType === 2) {
scores.value.push(msg.target);
power.value = msg.target.battery;
}
if (playersScores.value[msg.userId])
playersScores.value[msg.userId].push(msg.target);
}
if (msg.constructor === MESSAGETYPES.CurrentRoundEnded) {
if (room.value.battleType === 1) {
const result = msg.preRoundResult;
scores.value = [];
blueScores.value = [];
currentShooterId.value = 0;
currentBluePoint.value = result.blueScore;
currentRedPoint.value = result.redScore;
bluePoints.value += result.blueScore;
redPoints.value += result.redScore;
showRoundTip.value = true;
if (
result.currentRound > 0 &&
result.currentRound < totalRounds.value
) {
// 开始下一轮;
roundResults.value = result.roundResults;
currentRound.value = result.currentRound + 1;
}
}
}
if (msg.constructor === MESSAGETYPES.HalfTimeOver) {
startCount.value = false;
halfTimeTip.value = true;
tips.value = "准备下半场";
}
if (msg.constructor === MESSAGETYPES.MatchOver) {
uni.redirectTo({
url: `/pages/battle-result?battleId=${msg.id}`,
});
}
}
});
}
const destroyRoom = async () => {
if (roomNumber.value) 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 && !battleId.value) {
exitRoomAPI(roomNumber.value);
}
});
</script>
<template>
<Container title="对战" :onBack="() => (showModal = true)">
<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"
/>
<TestDistance :guide="false" />
</view>
<view v-if="step === 3">
<ShootProgress
:tips="tips"
:seq="seq"
:start="start && startCount"
:total="total"
/>
<PlayersRow
v-if="room.battleType === 1"
:blueTeam="blueTeam"
:redTeam="redTeam"
:currentShooterId="currentShooterId"
/>
<BowTarget
:mode="room.battleType ? 'team' : 'solo'"
:showE="start && user.id === currentShooterId"
:power="start ? power : 0"
:currentRound="currentRound"
:totalRound="totalRounds"
:scores="scores"
:blueScores="blueScores"
/>
<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] && playersScores[player.id].length === 12
"
/>
</view>
<Timer :seq="timerSeq" />
<ScreenHint :show="showRoundTip" :onClose="() => (showRoundTip = false)">
<RoundEndTip
:round="currentRound - 1"
:bluePoint="currentBluePoint"
:redPoint="currentRedPoint"
:roundData="roundResults[roundResults.length - 1]"
/>
</ScreenHint>
<ScreenHint
:show="halfTimeTip"
mode="small"
:onClose="() => (halfTimeTip = false)"
>
<view class="half-time-tip">
<text>上半场结束休息一下吧:</text>
<text>20秒后开始下半场</text>
</view>
</ScreenHint>
<SModal :show="showModal" :onClose="() => (showModal = false)">
<view class="btns">
<block v-if="!battleId">
<SButton :onClick="exitRoom" width="200px" :rounded="20">
暂时离开
</SButton>
<block v-if="owner.id === user.id">
<view :style="{ height: '20px' }"></view>
<SButton :onClick="destroyRoom" width="200px" :rounded="20">
解散房间
</SButton>
</block>
</block>
<block v-else>
<SButton :onClick="exitRoom" width="200px" :rounded="20">
退出比赛
</SButton>
</block>
</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: 107vw;
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;
}
</style>