367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
|
/*
|
||
|
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
|
||
|
|
||
|
https://www.cocos.com/
|
||
|
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
of this software and associated documentation files (the "Software"), to deal
|
||
|
in the Software without restriction, including without limitation the rights to
|
||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||
|
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||
|
subject to the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be included in
|
||
|
all copies or substantial portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
import { _decorator, Node, find, Vec3, v3, game } from 'cc';
|
||
|
import { Action } from '../../core/action/action';
|
||
|
import { Save } from '../data/save';
|
||
|
import { Msg } from '../../core/msg/msg';
|
||
|
import { Singleton } from '../../core/pattern/singleton';
|
||
|
import { Res } from '../../core/res/res';
|
||
|
import { ResCache } from '../../core/res/res-cache';
|
||
|
import { Actor } from '../actor/actor';
|
||
|
import { DropItem } from '../item/drop-item';
|
||
|
import { NavSystem } from '../navigation/navigation-system';
|
||
|
import { DataEquipInst, DataNavigationInst } from '../data/data-core';
|
||
|
import { fun } from '../../core/util/fun';
|
||
|
|
||
|
const { ccclass, property } = _decorator;
|
||
|
|
||
|
export class Level extends Singleton {
|
||
|
|
||
|
// Action objects are used to execute the current set of actions.
|
||
|
_action: Action | undefined;
|
||
|
|
||
|
// Level data object to store static game data.
|
||
|
_data: { [key: string]: any } = {};
|
||
|
|
||
|
// Level time.
|
||
|
_time: number = 0;
|
||
|
|
||
|
// The state at the beginning of the level.
|
||
|
_isStart = false;
|
||
|
|
||
|
// The spawn position of the player's level.
|
||
|
_spawn_pos = v3(0, 2, 0);
|
||
|
|
||
|
// The score rate of the level.
|
||
|
_scoreRate: number = 0;
|
||
|
|
||
|
// The player's game object.
|
||
|
_player: Actor | undefined;
|
||
|
|
||
|
// List of nodes of level enemies.
|
||
|
_enemies: Node[] = [];
|
||
|
|
||
|
// The root node of all objects at game runtime.
|
||
|
_objectNode: Node | null | undefined;
|
||
|
|
||
|
// Current upgrade cards.
|
||
|
currentCards: Array<{ name: string; info: any; }> = new Array(3);
|
||
|
|
||
|
// Level stop.
|
||
|
stop = false;
|
||
|
|
||
|
/**
|
||
|
* Initialize the level object.
|
||
|
*/
|
||
|
public init (): void {
|
||
|
|
||
|
// Get the level data and copy it for storage.
|
||
|
this._data = Object.assign(ResCache.Instance.getJson('data-level').json);
|
||
|
|
||
|
// Create an action object to manage the action of the level.
|
||
|
this._action = new Action('action-level');
|
||
|
|
||
|
// Find the root node of all objects.
|
||
|
this._objectNode = find('init')?.getChildByName('objects');
|
||
|
|
||
|
// Register external message access function mapping.
|
||
|
Msg.on('msg_level_start', this.onLevelStart.bind(this));
|
||
|
Msg.on('level_action', this.levelAction.bind(this));
|
||
|
Msg.on('level_do', this.do.bind(this));
|
||
|
Msg.on('msg_add_enemy', this.addEnemy.bind(this));
|
||
|
Msg.on('msg_add_item', this.addDrop.bind(this));
|
||
|
Msg.on('msg_replay', this.onReplay.bind(this));
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Executes the function with the name specified by the current object.
|
||
|
* @param fun Name of the function to be executed.
|
||
|
*/
|
||
|
public do (fun: string) {
|
||
|
this[fun]();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function is used to set the behavior related to the start of the level.
|
||
|
*/
|
||
|
public onLevelStart () {
|
||
|
|
||
|
// Set level stop is false.
|
||
|
this.stop = false;
|
||
|
this._isStart = true;
|
||
|
|
||
|
// Switch to the next statistic.
|
||
|
Save.Instance.nextStatistics();
|
||
|
|
||
|
// Initialize the current path finding data.
|
||
|
NavSystem.Init(DataNavigationInst._data);
|
||
|
|
||
|
this.levelAction('start');
|
||
|
}
|
||
|
|
||
|
public pause () {
|
||
|
this.stop = true;
|
||
|
}
|
||
|
|
||
|
public resume () {
|
||
|
this.stop = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method is used to restart the game.
|
||
|
*/
|
||
|
public onReplay () {
|
||
|
fun.delay(() => {
|
||
|
Msg.emit('push', 'level');
|
||
|
}, 2);
|
||
|
}
|
||
|
|
||
|
public levelAction (name: string) {
|
||
|
this._action!.on(name);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Added level role method.
|
||
|
* Used to initialize the character game object.
|
||
|
*/
|
||
|
public addPlayer () {
|
||
|
|
||
|
// Get a random node from the navigation system.
|
||
|
//const point = NavSystem.randomPoint();
|
||
|
|
||
|
// Get the player's prefab object from the resource cache.
|
||
|
const prefab = ResCache.Instance.getPrefab(this._data.prefab_player);
|
||
|
|
||
|
// Instantiate player level game object.
|
||
|
const resPlayer = Res.inst(prefab, this._objectNode!, this._data.spawn_pos);
|
||
|
|
||
|
// Get the Actor from the player level game object.
|
||
|
this._player = resPlayer.getComponent(Actor)!;
|
||
|
|
||
|
// Detect if this actor exists
|
||
|
if (this._player === null) {
|
||
|
throw new Error(`Level add player can not bind Actor Component.`);
|
||
|
}
|
||
|
|
||
|
this._player.bulletBox = 5;
|
||
|
|
||
|
// Set the player tag value of this actor to true.
|
||
|
this._player.isPlayer = true;
|
||
|
|
||
|
// Initialize the player object.
|
||
|
this._player.init('data-player');
|
||
|
|
||
|
// Update player hp.
|
||
|
this._player.updateHP();
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add level enemy method.
|
||
|
* @param res Add enemy resource name.
|
||
|
* @param groupID Enemy group id.
|
||
|
* @returns Enemy game object.
|
||
|
*/
|
||
|
public addEnemy (data: { res: string, groupID: number }) {
|
||
|
|
||
|
// Get a random node from the navigation system.
|
||
|
const point = NavSystem.randomPoint();
|
||
|
|
||
|
// Get the enemy's prefab object from the resource cache.
|
||
|
var prefab = ResCache.Instance.getPrefab(data.res);
|
||
|
|
||
|
// Instantiate enemy level game object.
|
||
|
var enemy = Res.inst(prefab, this._objectNode!, point.position);
|
||
|
|
||
|
enemy.name = data.res;
|
||
|
const actor = enemy.getComponent(Actor);
|
||
|
if (!actor) {
|
||
|
console.error('error inst enemy lose actor component. the name is :', data.res);
|
||
|
return;
|
||
|
}
|
||
|
actor._groupIndex = data.groupID;
|
||
|
actor.init(`data-${data.res}`);
|
||
|
actor.bulletBox = 9999;
|
||
|
actor.isReady = true;
|
||
|
this._enemies.push(enemy);
|
||
|
return enemy;
|
||
|
}
|
||
|
|
||
|
public removeEnemy (node: Node) {
|
||
|
for (let i = 0; i < this._enemies.length; i++) {
|
||
|
if (this._enemies[i] === node) {
|
||
|
this._enemies.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public addDrop (_data: { res: string, pos: Vec3 | undefined, groupIndex: number }) {
|
||
|
if (_data.pos === undefined) {
|
||
|
const point = NavSystem.randomPoint();
|
||
|
_data.pos = point.position;
|
||
|
}
|
||
|
const prefab = ResCache.Instance.getPrefab(this._data.prefab_drop_item);
|
||
|
const dropNode = Res.inst(prefab, this._objectNode!, _data.pos);
|
||
|
dropNode.name = _data.res;
|
||
|
|
||
|
const drop = dropNode.getComponent(DropItem);
|
||
|
const data = DataEquipInst.get(_data.res);
|
||
|
|
||
|
if (drop === null) {
|
||
|
throw new Error(`Drop node can not add component Drop Item.`);
|
||
|
}
|
||
|
|
||
|
drop.init(_data.res, data.drop_effect_index, _data.groupIndex);
|
||
|
|
||
|
}
|
||
|
|
||
|
public addObj (res: string) {
|
||
|
const point = NavSystem.randomPoint();
|
||
|
var prefab = ResCache.Instance.getPrefab(res);
|
||
|
var objNode = Res.inst(prefab, this._objectNode!, point.position);
|
||
|
return objNode;
|
||
|
}
|
||
|
|
||
|
public update (deltaTime: number): void {
|
||
|
if (!this._isStart) return;
|
||
|
|
||
|
if (this.stop) return;
|
||
|
|
||
|
this._time += deltaTime;
|
||
|
this._action!.update(deltaTime);
|
||
|
|
||
|
Msg.emit('msg_update_map');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select a skill card to update player attributes.
|
||
|
* @param selectIndex Select upgrade card index.
|
||
|
*/
|
||
|
public upgradePlayerAttributes (selectIndex: number) {
|
||
|
// Get upgrade values.
|
||
|
const upgradeValues = this.currentCards[selectIndex].info.values;
|
||
|
// Upgrade player data.
|
||
|
const length = upgradeValues.length;
|
||
|
//Update all attributes of the card.
|
||
|
for (let i = 0; i < length; i++) {
|
||
|
console.log(upgradeValues[i]);
|
||
|
const data = upgradeValues[i];
|
||
|
this._player!._data[data.name] = data.value;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public getUpgradeCardInfo (selectIndex: number) {
|
||
|
return this.currentCards[selectIndex].info.describe;
|
||
|
}
|
||
|
|
||
|
public gameOver () {
|
||
|
|
||
|
// Set level stop is true.
|
||
|
this.stop = true;
|
||
|
this._isStart = false;
|
||
|
Msg.emit('msg_stat_time', { key: 'play', time: this._time });
|
||
|
this.calculateScore();
|
||
|
this._enemies = [];
|
||
|
Save.Instance.saveGameOver(this._time, this._scoreRate);
|
||
|
this._player = undefined;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Calculate level score.
|
||
|
*/
|
||
|
public calculateScore () {
|
||
|
|
||
|
// Save day.
|
||
|
let day = Save.Instance.get('day');
|
||
|
if (day === undefined) day = 0;
|
||
|
else day++;
|
||
|
Save.Instance.setValue('day', day);
|
||
|
|
||
|
// Get killed number.
|
||
|
const killedTimes = Save.Instance.getStatistics('killedTime');
|
||
|
|
||
|
// Calculate hit rate.
|
||
|
const hitBodyTimes = Save.Instance.getStatistics('hit_bodyTimes');
|
||
|
const hitHeadTimes = Save.Instance.getStatistics('hit_headTimes');
|
||
|
let fireTimes = Save.Instance.getStatistics('fireTimes');
|
||
|
const hitRate = fireTimes == 0 ? 0 : ((hitBodyTimes + hitHeadTimes) / fireTimes);
|
||
|
Save.Instance.setStatistics('hit_rate', Number(hitRate.toFixed(4)));
|
||
|
|
||
|
// Calculate be hit times.
|
||
|
const beHitBodyTimes = Save.Instance.getStatistics('be_hit_bodyTimes');
|
||
|
const beHitHeadTimes = Save.Instance.getStatistics('be_hit_headTimes');
|
||
|
const beHitTimes = beHitBodyTimes + beHitHeadTimes;
|
||
|
Save.Instance.setStatistics('be_hit_times', beHitTimes);
|
||
|
|
||
|
// Calculate dodge rate.
|
||
|
const enemyFireTimes = Math.max(beHitTimes, Save.Instance.getStatistics('enemy_fireTimes'));
|
||
|
const dodgeRate = enemyFireTimes == 0 ? 0 : (1 - beHitTimes / enemyFireTimes);
|
||
|
Save.Instance.setStatistics('dodge_rate', Number(dodgeRate.toFixed(4)));
|
||
|
|
||
|
// Calculate level score.
|
||
|
// level score = killed * killed_to_score + hitRate * eachRateValue + dodgeRate * eachRateValue + survivalTime * survival_time_to_score
|
||
|
const eachRateValue = this._data.each_rate_value;
|
||
|
const level_score = Math.floor(killedTimes * this._data.killed_to_score + hitRate * eachRateValue + dodgeRate * eachRateValue + this._time * this._data.survival_time_to_score);
|
||
|
Save.Instance.setStatistics('level_score', level_score);
|
||
|
|
||
|
// Calculate final score rate.
|
||
|
const scoreLevels = this._data.score_level;
|
||
|
let passLevel = true;
|
||
|
this._scoreRate = scoreLevels.length - 1;
|
||
|
for (let i = 0; i < scoreLevels.length; i++) {
|
||
|
const infos = scoreLevels[i];
|
||
|
passLevel = true;
|
||
|
for (let k in infos) {
|
||
|
if (k == 'score') continue;
|
||
|
if (Save.Instance._currentStatistics[k] < infos[k]) {
|
||
|
passLevel = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (passLevel) {
|
||
|
this._scoreRate = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Save score rate.
|
||
|
Save.Instance.setStatistics('score_rate', this._scoreRate);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get final score rating
|
||
|
* @returns
|
||
|
*/
|
||
|
public getLevelScore () {
|
||
|
return this._data.score_level[this._scoreRate].score;
|
||
|
}
|
||
|
|
||
|
}
|