猫史档案馆


喵战模板击杀数排行榜源代码

用户:帅气的导弹鲨leZl帅气的导弹鲨leZl查看:0 回复:0 评论:0 创建时间:2023-08-23T18:45:10


console.clear()
// ------------配置------------
const DEFAULT_WEAPON = '冲锋喵'; // 初始武器

// 队伍配置
const TEAM_CONFIG = {
    '红队': {
        spawnPoint: '红队出生点', // 出生点实体名称
        color: new Box3RGBColor(1, 0, 0), // 颜色
    },
    '黄队': {
        spawnPoint: '黄队出生点',
        color: new Box3RGBColor(1, 1, 0),
    },
};
// 武器配置
const WEAPON = {
    '喵喵': {
        damage: 30, // 伤害
        distance: 10, // 射程距离
        interval: 30, // 发射间隔(数值越大, 射速越慢)
        orientation: new Box3Quaternion(0, 1, 0, 0).rotateZ(-Math.PI / 2), // 装备方向
    },
    '冲锋喵': {
        damage: 10,
        distance: 30,
        interval: 5,
        orientation: new Box3Quaternion(0, 1, 0, 0).rotateZ(-Math.PI / 2),
    },
    '喵': {
        damage: 25,
        distance: 50,
        interval: 40,
        orientation: new Box3Quaternion(0, 1, 0, 0).rotateZ(-Math.PI / 2).rotateX(Math.PI),
    },
};
const GAME_END_TIME = 10 * 60; // 游戏结束时间, 单位秒
const GAME_END_SCORE = 30; // 游戏结束分数
const MIN_GAME_PLAYER_NUM = 2; // 最小游戏人数
const GAME_HALFTIME = 5; // 两局游戏间隔时间, 单位秒
// ----------------------------
const spawnPoint = objectMap(TEAM_CONFIG, ({spawnPoint}, team) => {
    const e = world.querySelector('#' + spawnPoint);
    e.onEntityContact(({entity, other}) => {
        if (!other.isPlayer) return; // 不处理非玩家的碰撞
        const { name } = other.player;
        if (!(name in playerState)) return; // 不处理非游戏中玩家的碰撞
        if (playerState[name].team === team) {
            // 碰到自家出生点恢复全部HP
            if (other.hp < other.maxHp) {
                other.hp = other.maxHp;
                other.player.directMessage('你回复了全部的HP');
            }
        } else {
            // 碰到非自家出生点, HP减半
            other.hurt(Math.ceil(other.hp / 2), { attacker: entity });
        }
    });
    return e.position.clone(); // 返回出生点位置
});

const defaultWeaponMesh = world.querySelector('#' + DEFAULT_WEAPON).mesh;
let playerState = {}; // 玩家状态
let teamScore = {}; // 队伍分数
let teamNum = {}; // 队伍人数
let gameEndResolve; // 用于结束游戏的函数

// 主函数
(async function main() {
    // 打印开始时间的日志
    console.log(`-------[${new Date().toISOString()}]--------`)
    setup(); // 初始化
    // 主循环, 不断检测并开始新的游戏
    for(;;await sleep(GAME_HALFTIME * 1000)) {
        // 检测人数
        if (world.querySelectorAll('player').length < MIN_GAME_PLAYER_NUM) {
            world.say(`当前人数不足${MIN_GAME_PLAYER_NUM}人, 无法开始游戏!`);
            continue;
        }
        // 倒计时
        world.say(`游戏即将开始!`);
        for (let i = 0; i > 0; i--) {
            await sleep(1000);
            world.say(`${i}...`);
        }
        // 开始新的一局游戏
        await start();
    }
})();

// 开始游戏
async function start() {
    console.log(`[${new Date().toISOString()}]开始新的一局游戏`);
    // 清空分数和队伍人数
    teamScore = objectMap(TEAM_CONFIG, () => 0);
    teamNum = objectMap(TEAM_CONFIG, () => 0);
    // 获取队伍名称
    const teamNames = Object.keys(TEAM_CONFIG);
    // 打乱所有玩家并执行队伍分配
    arrayShuffle(world.querySelectorAll('player')).forEach((entity, i) => {
        const teamName = teamNames[i % teamNames.length];
        joinTeam(entity, teamName);
        entity.player.directMessage(`你被分配到了${teamName}`);
    });
    // 等待游戏结束
    await new Promise(async (resolve) => {
        gameEndResolve = resolve; // 设置游戏结束函数
        await sleep(GAME_END_TIME * 1000); // 等待GAME_END_TIME秒
        resolve(); // 如果游戏没有提前结束, 就会由于到时间而结束
    });
    // 广播获胜队伍
    const winner = getMaxScore();
    world.say(`${winner}胜利, 总分: ${teamScore[winner]}`);
    let maxKd = 0; // 最高的kd
    let maxKdPlayer; // 最高kd的玩家
    // 计算每个玩家的KD并记录最高值
    for (const name in playerState) {
        const { entity, k, d } = playerState[name];
        const kd = k / (d + 1); // 计算公式: 击杀数/(喵数+1)
        // 告知每个玩家本局的kd
        entity.player.directMessage(`你本场游戏的评分为${kd.toFixed(2)}`);
        // 更新最大kd
        if (kd > maxKd) {
            maxKd = kd;
            maxKdPlayer = name;
        }
        // 喵家设置为非游戏状态
        entity.enableDamage = false;
        entity.player.cameraMode = 'FOLLOW';
        entity.player.color.set(1, 1, 1);
    }
    // 广播MVP(kd最高者)
    world.say(`本场游戏MVP为@${maxKdPlayer}, 评分是${maxKd.toFixed(2)}`);
    // 清空玩家状态和结束函数
    playerState = {};
    gameEndResolve = undefined;
}

// 加入队伍
function joinTeam(entity, team) {
    teamNum[team]++; // 队伍人数+1
    // 设置玩家属性
    entity.enableDamage = true;
    entity.player.cameraMode = 'FPS';
    entity.player.color.copy(TEAM_CONFIG[team].color);
    // 初始化玩家状态
    playerState[entity.player.name] = {
        team,
        entity,
        喵: DEFAULT_WEAPON,
        shootTick: 0,
        k: 0,
        d: 0,
    };
    // 装备武器
    entity.player.addWearable({
        bodyPart: Box3BodyPart.RIGHT_HAND, // 装备在右手上
        mesh: defaultWeaponMesh, // 设置武器mesh
        orientation: WEAPON[DEFAULT_WEAPON].orientation, // 调整方向
        scale: new Box3Vector3(0.3, 0.4, 0.2), // 调整大小
        offset: new Box3Vector3(0, -0.5, 0), //调整位置
    });
    entity.position.copy(spawnPoint[team]); // 传送到队伍的出生点
    entity.position.y = 10 + Math.random() * 20; // 随机增加y的偏移, 避免多个玩家相撞
    entity.player.spawnPoint.copy(entity.position); // 设置出生点
}

// 判断是否是游戏中的玩家
function isPlayer(entity) {
    return entity && entity.isPlayer && entity.player.name in playerState;
}

// 获取最高分的队伍名称
function getMaxScore() {
    let num = 0;
    let team;
    for (const key in teamScore) {
        if (teamScore[key] > num) {
            team = key;
            num = teamScore[key];
        }
    }
    return team;
}

// 获取最低分的队伍名称
function getMinTeam() {
    let num = Infinity;
    let team;
    for (const key in teamNum) {
        if (teamNum[key] < num) {
            team = key;
            num = teamNum[key];
        }
    }
    return team;
}

// 对话框内容
function dialogContent() {
    // 首先获取当前世界中的所有玩家
    const allPlayerEntities = world.querySelectorAll('player');
    // 然后将所有的玩家进行排序,排序的规则是按照玩家持有的金币数量降序
    const sortedPlayerEntities = allPlayerEntities.sort((a, b)=>  b.player.coin - a.player.coin);
    
    
    // 接下来,我们将在排行榜上以: xxx 有 yyy 个金币 的格式展示,并且每一条信息换一行显示!

        // 首先声明一个content字符串,一会用来保存每一个玩家的信息
        let content = "";
    // 在这里需要做一个循环遍历,将刚才得到的有序的玩家数组遍历一遍
    for(const entity of sortedPlayerEntities ) {
        // 在遍历的过程中,每一次遍历,我们拿到当前的实体(entity),
        // 将实体上的玩家名字(player.name)以及玩家金币数量(player.coin)拼接成排行榜上的一行信息
        // 这样就得到: xxx 有 yyy 个金币 的格式啦
        // 我们在这个文字的末尾加一个\n, 这样我们就能实现换行的效果
        // 紧接着,再把这行信息加上content,这样做是为了把每一个玩家的排行榜信息拼接起来,保存到content中
        content = content + `${entity.player.name}击杀了${entity.player.coin}次\n`
    }
    // 最后,把得到的完整的带有玩家名称和金币数量,并且会换行的排行榜数据(content)返回出去
    return content
}

// 初始化
function setup() {
    // 处理喵
    world.onDie(({entity, attacker}) => {
        if (!attacker || !isPlayer(entity)) return; // 不处理出无击杀者的喵和非玩家的喵
        // 获取喵玩家名称和击杀者名称
        const { name } = entity.player;
        const killerName = attacker.isPlayer ? attacker.player.name : attacker.id;
        playerState[name].d++; // 喵数+1
        // 广播喵情况
        world.say(`@${killerName}击败了@${name}, 当前比分${Object.keys(teamScore).map((name) => `${name}:${teamScore[name]}`).join(', ')}`);

        // 如果击杀者是玩家
        if (attacker.isPlayer) {
            entity.player.coin++;
            playerState[killerName].k++; // 击杀数+1
            teamScore[playerState[killerName].team]++; // 队伍分数+1
            // 判断是否达到比赛结束的比分
            if (teamScore[playerState[killerName].team] >= GAME_END_SCORE) gameEndResolve();
        }
    });
    // 初始化玩家
    world.onPlayerJoin(({ entity}) => {
        entity.player.coin = 0
        if (gameEndResolve) {
            // 游戏已经开始, 则加入人数组少的队伍
            const team = getMinTeam()
            joinTeam(entity, team);
            world.say(`玩家@${entity.player.name}加入${team}`);
        }
        // 处理玩家鼠标点击
        entity.player.onPress(({ tick, entity, button, raycast: { hitEntity, direction, distance } }) => {
            // 喵后点鼠标重生
            if (isPlayer(entity) && entity.hp === 0) {
                return entity.player.forceRespawn();
            }
            // 判断是否是鼠标左键, 击中实体是否是玩家, 以及击中的玩家是否喵
            if (button === 'action0' && isPlayer(entity) && isPlayer(hitEntity) && !hitEntity.dead) {
                // 获取双方的状态
                const state = playerState[entity.player.name];
                const otherState = playerState[hitEntity.player.name];
                if (state.team === otherState.team) return; // 相同队伍不处理
                const 喵 = WEAPON[state.喵]; // 获取武器信息
                if (state.shootTick + 喵.interval > tick) return; // 小于武器间隔事件不处理
                state.shootTick = tick; // 更新射击时间(tick)
                // 设置击中效果
                hitEntity.velocity.x += direction.x;
                //hitEntity.velocity.y += 2;
                hitEntity.velocity.z += direction.z;
                // 计算伤害并应用
                const basicDamage = Math.max(1, 喵.damage * Math.min(1, 喵.distance / distance));
                const damage = Math.ceil(basicDamage * (1 + Math.random() * 0.3));
                hitEntity.hurt(damage, { attacker: entity });
            }
        });
    });
    // 获取武器
    world.onEntityContact(({ entity, other }) => {
        // 必须是玩家触碰武器
        if (isPlayer(entity) && other.id in WEAPON) {
            const state = playerState[entity.player.name]; // 获取玩家状态
            if (!other.hasTag(state.team)) return; // 检查队伍
            state.喵 = other.id; // 设置武器
            entity.player.directMessage(`你获得了${other.id}`); // 通知玩家
            // 更新装备
            const handWearable = entity.player.wearables(Box3BodyPart.RIGHT_HAND)[0];
            handWearable.mesh = other.mesh;
            handWearable.orientation = WEAPON[other.id].orientation;
        }
        
    });
    // 收到伤害的通知
    world.onTakeDamage(({entity, damage, attacker}) => {
        if (!entity.isPlayer) return;
        const attackerName = attacker.isPlayer ? attacker.player.name : attacker.id;
        entity.player.directMessage(`[剩余HP: ${entity.hp}]: 你受到了${damage}点来自@${attackerName}的伤害`);
    });
    world.onPress(({entity,button})=>{
        if (button == "action1"){
            entity.player.dialog({
                type:Box3DialogType.TEXT,
                title:"击杀排行榜",
                content:dialogContent(),
            })
        }
    })
}

// 随机打乱数组
function arrayShuffle (array) {
    for (let i = 1; i < array.length; ++i) {
        const temp = array[i];
        const x = (Math.random() * (i + 1)) | 0;
        array[i] = array[x];
        array[x] = temp;
    }
    return array;
}

// 为对象实现类似数组的map功能
function objectMap(obj, func) {
    const o = {};
    for (const key in obj) {
        o[key] = func(obj[key], key);
    }
    return o;
}


回复

上一页1 页 / 共 0下一页