清理战场 - 战斗结算

在上一课中,我们终于确定了赢家,现在是清理战场的时候了,赢家得到奖励,输家受到惩罚。

战斗对双方城堡的影响包括:

  • 战斗冷却
  • 士兵伤亡
  • 掠夺在战斗冷却期间由失败者的基础经济能力提供的利益
  • 胜者获得经验值

让我们继续完成 battle 函数。我们将分别结算赢家和输家。

1. 赢家

我们需要首先结算城堡的经济:

// 5. 战斗结算   
// 5.1 结算赢家
core::settle_castle_economy_inner(clock, &mut winner);

士兵剩余和士兵损失的计算方式如下:

use move_castle::utils;

let winner_solders_total_defense_power = core::get_castle_total_soldiers_defense_power(&winner);
let loser_solders_total_attack_power = core::get_castle_total_soldiers_attack_power(&loser);
let winner_soldiers_left;
if (winner_solders_total_defense_power > loser_solders_total_attack_power) {
    let (_, winner_soldier_defense_power) = core::get_castle_soldier_attack_defense_power(core::get_castle_race(&winner));
    winner_soldiers_left = math::divide_and_round_up(winner_solders_total_defense_power - loser_solders_total_attack_power, winner_soldier_defense_power);
} else {
    winner_soldiers_left = 0;
};
let winner_soldiers_lost = core::get_castle_soldiers(&winner) - winner_soldiers_left;

上面的代码中使用的 get_castle_race 函数在 core 模块中:

public(package) fun get_castle_race(castle_data: &CastleData): u64 {
    castle_data.race
}

utils.move 中的 abs_minus 函数是:

public(package) fun abs_minus(a: u64, b: u64): u64 {
    let result;
    if (a > b) {
        result = a - b;
    } else {
        result = b - a;
    };
    result
}

core.move 中的 get_castle_soldiers 是:

public(package) fun get_castle_soldiers(castle_data: &CastleData): u64 {
    castle_data.millitary.soldiers
}

胜利者获得的经验值取决于城堡的等级,根据设计,在 core.move 中添加这个常量向量和函数:

/// 根据胜利者等级在战斗中获得的经验值 1 - 10
const BATTLE_EXP_GAIN_LEVELS : vector<u64> = vector[25, 30, 40, 55, 75, 100, 130, 165, 205, 250];

public fun battle_winner_exp(castle_data: &CastleData): u64 {
    let battle_exp_map = BATTLE_EXP_GAIN_LEVELS;
    *vector::borrow<u64>(&battle_exp_map, castle_data.level)
}

回到 battle 函数,添加:

let winner_exp_gain = core::battle_winner_exp(&winner);

被掠夺的失败者基础经济能力应该是:

let reparation_economic_power = core::get_castle_economic_base_power(&loser);

更新赢家的城堡数据:

core::battle_settlement_save_castle_data(
    game_store,
    winner, 
    true, 
    current_timestamp + BATTLE_WINNER_COOLDOWN_MS,
    reparation_economic_power,
    current_timestamp,
    current_timestamp + BATTLE_LOSER_ECONOMIC_PENALTY_TIME,
    winner_soldiers_left,
    winner_exp_gain
);

const BATTLE_WINNER_COOLDOWN_MS : u64 = 30 * 1000; // 30秒
const BATTLE_LOSER_ECONOMIC_PENALTY_TIME : u64 = 2 * 60 * 1000; // 2分钟

core.move 中:

public(package) fun get_castle_economic_base_power(castle_data: &CastleData): u64 {
    castle_data.economy.base_power
}

// 计算士兵经济能力
public(package) fun calculate_soldiers_economic_power(count: u64): u64 {
    SOLDIER_ECONOMIC_POWER * count
}

/// 结算战斗
public(package) fun battle_settlement_save_castle_data(game_store: &mut GameStore, mut castle_data: CastleData, win: bool, cooldown: u64, economic_base_power: u64, current_timestamp: u64, economy_buff_end: u64, soldiers_left: u64, exp_gain: u64) {
    // 1. 战斗冷却
    castle_data.millitary.battle_cooldown = cooldown;
    // 2. 士兵剩余
    castle_data.millitary.soldiers = soldiers_left;
    castle_data.economy.soldier_buff.power = calculate_soldiers_economic_power(soldiers_left);
    castle_data.economy.soldier_buff.start = current_timestamp;
    // 3. 士兵导致的总攻击/防御力量
    castle_data.millitary.total_attack_power = get_castle_total_attack_power(&castle_data);
    castle_data.millitary.total_defense_power = get_castle_total_defense_power(&castle_data);
    // 4. 获得经验值
    castle_data.experience_pool = castle_data.experience_pool + exp_gain;
    // 5. 经济增益
    vector::push_back(&mut castle_data.economy.battle_buff, EconomicBuff {
        debuff: !win,
        power: economic_base_power,
        start: current_timestamp,
        end: economy_buff_end,
    });
    // 6. 放回表中
    dynamic_field::add(&mut game_store.id, castle_data.id, castle_data);
}

这不是一个好的实践,这个函数有太多参数。

2. 输家

同样,在结算失败者的战斗结果之前,有必要结算其经济:

// 5.2 结算输家
core::settle_castle_economy_inner(clock, &mut loser);

输家的数字相当简单,士兵保持为0,获得的经验值为0。

let loser_soldiers_left = 0;
let loser_soldiers_lost = core::get_castle_soldiers(&loser) - loser_soldiers_left;
core::battle_settlement_save_castle_data(
    game_store,
    loser, 
    false, 
    current_timestamp + BATTLE_LOSER_COOLDOWN_MS,
    reparation_economic_power,
    current_timestamp,
    current_timestamp + BATTLE_LOSER_ECONOMIC_PENALTY_TIME,
    loser_soldiers_left,
    0
);

const BATTLE_LOSER_COOLDOWN_MS : u64 = 2 * 60 * 1000; // 2分钟

最后,让我们重新审视完整的 battle 函数:

entry fun battle(castle: &Castle, clock: &Clock, game_store: &mut GameStore, ctx: &mut TxContext) {
    // 1. 随机选择一个目标
    let attacker_id = object::id(castle);
    let target_id = core::random_battle_target(attacker_id, game_store, ctx);

    // 2. 获取城堡数据
    let (attacker, defender) = core::fetch_castle_data(attacker_id, target_id, game_store);

    // 3. 检查战斗冷却时间
    let current_timestamp = clock::timestamp_ms(clock);
    assert!(core::get_castle_battle_cooldown(&attacker) < current_timestamp, EBattleCooldown);
    assert!(core::get_castle_battle_cooldown(&defender) < current_timestamp, EBattleCooldown);

    // 4. 战斗
    // 4.1 计算总攻击力和防御力
    let mut attack_power = core::get_castle_total_attack_power(&attacker);
    let mut defense_power = core::get_castle_total_defense_power(&defender);
    if (core::has_race_advantage(&attacker, &defender)) {
        attack_power = math::divide_and_round_up(attack_power * 15, 10)
    } else if (core::has_race_advantage(&defender, &attacker)) {
        defense_power = math::divide_and_round_up(defense_power * 15, 10)
    };

    // 4.2 确定胜负
    let (mut winner, mut loser);
    if (attack_power > defense_power) {
        winner = attacker;
        loser = defender;
    } else {
        winner = defender;
        loser = attacker;
    };
    let winner_id = core::get_castle_id(&winner);
    let loser_id = core::get_castle_id(&loser);

    // 5. 战斗结算   
    // 5.1 结算胜者
    core::settle_castle_economy_inner(clock, &mut winner);
    let winner_solders_total_defense_power = core::get_castle_total_soldiers_defense_power(&winner);
    let loser_solders_total_attack_power = core::get_castle_total_soldiers_attack_power(&loser);
    let winner_soldiers_left;
    if (winner_solders_total_defense_power > loser_solders_total_attack_power) {
        let (_, winner_soldier_defense_power) = core::get_castle_soldier_attack_defense_power(core::get_castle_race(&winner));
        winner_soldiers_left = math::divide_and_round_up(winner_solders_total_defense_power - loser_solders_total_attack_power, winner_soldier_defense_power);
    } else {
        winner_soldiers_left = 0;
    };
    let winner_soldiers_lost = core::get_castle_soldiers(&winner) - winner_soldiers_left;
    let winner_exp_gain = core::battle_winner_exp(&winner);
    let reparation_economic_power = core::get_castle_economic_base_power(&loser);
    core::battle_settlement_save_castle_data(
        game_store,
        winner, 
        true, 
        current_timestamp + BATTLE_WINNER_COOLDOWN_MS,
        reparation_economic_power,
        current_timestamp,
        current_timestamp + BATTLE_LOSER_ECONOMIC_PENALTY_TIME,
        winner_soldiers_left,
        winner_exp_gain
    );

    // 5.2 结算败者
    core::settle_castle_economy_inner(clock, &mut loser);
    let loser_soldiers_left = 0;
    let loser_soldiers_lost = core::get_castle_soldiers(&loser) - loser_soldiers_left;
    core::battle_settlement_save_castle_data(
        game_store,
        loser, 
        false, 
        current_timestamp + BATTLE_LOSER_COOLDOWN_MS,
        reparation_economic_power,
        current_timestamp,
        current_timestamp + BATTLE_LOSER_ECONOMIC_PENALTY_TIME,
        loser_soldiers_left,
        0
    );

    // 6. 触发事件
    event::emit(CastleBattleLog {
        attacker: attacker_id,
        winner: winner_id,
        loser: loser_id,
        winner_soldiers_lost: winner_soldiers_lost,
        loser_soldiers_lost: loser_soldiers_lost,
        reparation_economic_power: reparation_economic_power,
        battle_time: current_timestamp,
        reparation_end_time: current_timestamp + BATTLE_LOSER_ECONOMIC_PENALTY_TIME
    });
}

const BATTLE_WINNER_COOLDOWN_MS : u64 = 30 * 1000; // 30 秒
const BATTLE_LOSER_COOLDOWN_MS : u64 = 2 * 60 * 1000; // 2 分钟
const BATTLE_LOSER_ECONOMIC_PENALTY_TIME : u64 = 2 * 60 * 1000; // 2 分钟