feat: add new1_50 B onboarding gates
This commit is contained in:
parent
f7335a5262
commit
6b921eb978
|
|
@ -254,6 +254,8 @@ export default class GameManager extends cc.Component {
|
|||
"layer_3": "layer_three"
|
||||
};
|
||||
const groupMap = {
|
||||
"1": 1,
|
||||
"2": 2,
|
||||
"A": 1,
|
||||
"B": 2,
|
||||
"C": 3,
|
||||
|
|
|
|||
|
|
@ -27,18 +27,18 @@ export default class ItemGuide extends cc.Component {
|
|||
this.itemList[1].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[1];
|
||||
this.itemList[2].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[2];
|
||||
this.itemList[3].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[12];
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == 11) {
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
this.itemList[0].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[3];
|
||||
this.itemList[1].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[4];
|
||||
this.itemList[2].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[5];
|
||||
this.itemList[3].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[12];
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == 16) {
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
this.itemList[0].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[6];
|
||||
this.itemList[1].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[7];
|
||||
this.itemList[2].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[8];
|
||||
this.itemList[3].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[12];
|
||||
}
|
||||
else if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true) {
|
||||
else if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true && cc.fx.GameConfig.isWinStreakUnlocked()) {
|
||||
this.itemList[0].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[9];
|
||||
this.itemList[1].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[10];
|
||||
this.itemList[2].getComponent(cc.Sprite).spriteFrame = this.itemSpriteList[11];
|
||||
|
|
@ -53,8 +53,8 @@ export default class ItemGuide extends cc.Component {
|
|||
const gameInfo = cc.fx.GameConfig.GM_INFO;
|
||||
const level = gameInfo.level + 1;
|
||||
const showPropGuide = (level === 8 && gameInfo.hammerFirst)
|
||||
|| (level === 11 && gameInfo.freezeFirst)
|
||||
|| (level === 16 && gameInfo.magicAFirst);
|
||||
|| (level === cc.fx.GameConfig.getPropUnlockLevel("time") && gameInfo.freezeFirst)
|
||||
|| (level === cc.fx.GameConfig.getPropUnlockLevel("magic") && gameInfo.magicAFirst);
|
||||
const map = MapConroler._instance;
|
||||
this.node.children.forEach((item) => {
|
||||
item.active = false;
|
||||
|
|
@ -68,14 +68,14 @@ export default class ItemGuide extends cc.Component {
|
|||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 8) {
|
||||
cc.fx.GameConfig.GM_INFO.hammerFirst = false;
|
||||
index = 0;
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == 11) {
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
cc.fx.GameConfig.GM_INFO.freezeFirst = false;
|
||||
index = 1;
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == 16) {
|
||||
} else if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
cc.fx.GameConfig.GM_INFO.magicAFirst = false;
|
||||
index = 2;
|
||||
}
|
||||
else if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true) {
|
||||
else if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true && cc.fx.GameConfig.isWinStreakUnlocked()) {
|
||||
cc.fx.GameConfig.GM_INFO.winStreakFirst = false;
|
||||
index = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -598,7 +598,7 @@ export default class JiaZai extends cc.Component {
|
|||
this.queueHomeAutomaticTask("订单补发检查", () => this.getOrder(), 2);
|
||||
} else if (cacheKey === "cachedMonthlyCardPrefab") {
|
||||
this.queueHomeAutomaticTask("月卡检查", () => {
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= 16 && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= cc.fx.GameConfig.getFeatureUnlockLevel("month") && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
this.monthH();
|
||||
}
|
||||
}, 1);
|
||||
|
|
@ -1028,7 +1028,7 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
|
||||
let top = this.node.getChildByName("Load").getChildByName("Top");
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= 16 && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= cc.fx.GameConfig.getFeatureUnlockLevel("month") && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
top.getChildByName("yicon").active = true;
|
||||
if (JiaZai.cachedMonthlyCardPrefab) {
|
||||
this.queueHomeAutomaticTask("已缓存月卡检查", () => this.monthH(), 1);
|
||||
|
|
@ -1063,6 +1063,7 @@ export default class JiaZai extends cc.Component {
|
|||
|
||||
top.getChildByName("gachaGuang").runAction(cc.rotateTo(15, 1080).repeatForever());
|
||||
this.refreshGachaRed();
|
||||
this.refreshNewPlayerActivityButtons();
|
||||
|
||||
this.setAdaptation();
|
||||
// 加载广告
|
||||
|
|
@ -1077,6 +1078,36 @@ export default class JiaZai extends cc.Component {
|
|||
this.initTopBarTouchFade();
|
||||
}
|
||||
|
||||
private refreshNewPlayerActivityButtons(): void {
|
||||
if (!cc.fx.GameConfig.isNewPlayerB()) return;
|
||||
const top = this.node.getChildByName("Load").getChildByName("Top");
|
||||
for (const [name, level] of [["day", 36], ["hammer", 51]] as [string, number][]) {
|
||||
const button = top.getChildByName(name);
|
||||
button.active = cc.fx.GameConfig.GM_INFO.level >= 20;
|
||||
this.setNewPlayerActivityLock(button, level);
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.level < 40) this.setJungleTreasureHomeButtonVisible(false);
|
||||
}
|
||||
|
||||
private setNewPlayerActivityLock(button: cc.Node, unlockLevel: number): void {
|
||||
const locked = cc.fx.GameConfig.GM_INFO.level + 1 < unlockLevel;
|
||||
button.getComponent(cc.Button).interactable = !locked;
|
||||
const labelName = button.name === "jungle" ? "time" : "unlockLevel";
|
||||
let labelNode = button.getChildByName(labelName);
|
||||
if (locked && !labelNode) {
|
||||
// 复用 Jungle 入口已有的文字样式。
|
||||
labelNode = cc.instantiate(this.getJungleTreasureHomeButton().getChildByName("time"));
|
||||
labelNode.name = labelName;
|
||||
button.addChild(labelNode);
|
||||
}
|
||||
if (labelNode) {
|
||||
labelNode.active = locked || button.name === "jungle";
|
||||
if (locked) labelNode.getComponent(cc.Label).string = `${unlockLevel}关解锁`;
|
||||
}
|
||||
const red = button.getChildByName("red");
|
||||
if (locked && red) red.active = false;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 初始化 Top 栏触摸滑动渐隐/渐显
|
||||
// 功能:
|
||||
|
|
@ -2154,6 +2185,7 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
//打开月卡
|
||||
openMonthlyCard(event?: cc.Event, customEventData?: string) {
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level < 20) return;
|
||||
if (!this.ensureHomePopupPrefab("shop", "prefab/monthlyCard", "cachedMonthlyCardPrefab",
|
||||
() => this.openMonthlyCard(undefined, customEventData))) return;
|
||||
|
||||
|
|
@ -2257,6 +2289,7 @@ export default class JiaZai extends cc.Component {
|
|||
// - share:是否来自分享回调
|
||||
// ============================================
|
||||
openDailyQuests(event?: cc.Event, customEventData?: string, share?: boolean) {
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level < 35) return;
|
||||
this.vibrateButtonClick(event);
|
||||
|
||||
let eventName = "";
|
||||
|
|
@ -3105,6 +3138,12 @@ export default class JiaZai extends cc.Component {
|
|||
private setJungleTreasureHomeButtonVisible(visible: boolean): void {
|
||||
let button = this.getJungleTreasureHomeButton();
|
||||
if (button) {
|
||||
if (cc.fx.GameConfig.isNewPlayerB()) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level < 40) {
|
||||
visible = cc.fx.GameConfig.GM_INFO.level >= 20;
|
||||
}
|
||||
this.setNewPlayerActivityLock(button, 41);
|
||||
}
|
||||
button.active = visible;
|
||||
}
|
||||
this.refreshHomeActivityEntryLayout();
|
||||
|
|
@ -3975,6 +4014,7 @@ export default class JiaZai extends cc.Component {
|
|||
// 2. 使用缓存控制每天只弹一次月卡弹窗
|
||||
// ============================================
|
||||
monthH() {
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level < 20) return;
|
||||
if (this.deferHomePopupWhileTransfer("monthlyCard", () => this.monthH())) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -4293,7 +4333,7 @@ export default class JiaZai extends cc.Component {
|
|||
if (cc.fx.GameConfig.GM_INFO.tasks.levelPass.state == 1 || cc.fx.GameConfig.GM_INFO.tasks.share.state == 1 ||
|
||||
cc.fx.GameConfig.GM_INFO.tasks.useEnergy.state == 1 || cc.fx.GameConfig.GM_INFO.tasks.useProp.state == 1
|
||||
) {
|
||||
top.getChildByName("day").getChildByName("red").active = true;
|
||||
top.getChildByName("day").getChildByName("red").active = !cc.fx.GameConfig.isNewPlayerB() || cc.fx.GameConfig.GM_INFO.level >= 35;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -4302,7 +4342,7 @@ export default class JiaZai extends cc.Component {
|
|||
if (cc.fx.GameConfig.GM_INFO.tasks.levelPass.state == 1 || cc.fx.GameConfig.GM_INFO.tasks.share.state == 1 ||
|
||||
cc.fx.GameConfig.GM_INFO.tasks.useEnergy.state == 1 || cc.fx.GameConfig.GM_INFO.tasks.useProp.state == 1
|
||||
) {
|
||||
top.getChildByName("day").getChildByName("red").active = true;
|
||||
top.getChildByName("day").getChildByName("red").active = !cc.fx.GameConfig.isNewPlayerB() || cc.fx.GameConfig.GM_INFO.level >= 35;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4315,7 +4355,7 @@ export default class JiaZai extends cc.Component {
|
|||
// 触发条件:连胜显示开关打开且关卡>=17级
|
||||
// ============================================
|
||||
openWin() {
|
||||
if (this.winStreakShow == false || cc.fx.GameConfig.GM_INFO.level < 17) {
|
||||
if (this.winStreakShow == false || cc.fx.GameConfig.GM_INFO.level < cc.fx.GameConfig.getFeatureUnlockLevel("winStreak")) {
|
||||
return;
|
||||
} else {
|
||||
let date = new Date();
|
||||
|
|
@ -4345,6 +4385,7 @@ export default class JiaZai extends cc.Component {
|
|||
// 何时调用:点击连胜活动按钮时调用
|
||||
// ============================================
|
||||
openWinStreak(event?: cc.Event, customEventData?: string, fromQueue: boolean = false) {
|
||||
if (!cc.fx.GameConfig.isWinStreakUnlocked()) return;
|
||||
if (!this.isHomeRuntimeAlive() || cc.fx.GameConfig.isGameplayEntryPreparing()) return;
|
||||
if (!event && customEventData === undefined && !fromQueue) {
|
||||
HomePopupQueue.get(this.node).enqueue("winStreak", 50, done => {
|
||||
|
|
@ -4466,7 +4507,7 @@ export default class JiaZai extends cc.Component {
|
|||
popUpPassCheck(retry: number = 0) {
|
||||
if (this.deferHomePopupWhileTransfer("passCheck", () => this.popUpPassCheck(retry))) return;
|
||||
const info = cc.fx.GameConfig.GM_INFO;
|
||||
if (info.level < 21 || !info.canIos) return;
|
||||
if (info.level < cc.fx.GameConfig.getFeatureUnlockLevel("pass") || !info.canIos) return;
|
||||
const date = new Date();
|
||||
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
const cacheKey = 'CACHE_PASS_CHECK:' + (info.uid || Utils.uid);
|
||||
|
|
@ -4519,6 +4560,10 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
|
||||
private openPassCheckIntro(onOpened?: () => void, onFinished?: () => void) {
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level < 20) {
|
||||
if (onFinished) onFinished();
|
||||
return;
|
||||
}
|
||||
if (this.passContentLoading || (this.passCheckNode && cc.isValid(this.passCheckNode))) { if (onFinished) onFinished(); return; }
|
||||
if (this.passIntroNode && cc.isValid(this.passIntroNode)) { if (onFinished) onFinished(); return; }
|
||||
if (!this.isHomeRuntimeAlive() || cc.fx.GameConfig.isGameplayEntryPreparing()) { if (onFinished) onFinished(); return; }
|
||||
|
|
@ -4543,6 +4588,7 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
|
||||
private openPassCheckContent(event?: cc.Event, customEventData?: string) {
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level < 20) return;
|
||||
if (this.passContentLoading || (this.passCheckNode && cc.isValid(this.passCheckNode))) return;
|
||||
this.vibrateButtonClick(event);
|
||||
if (!this.ensureHomePopupPrefab("shop", "prefab/passCheck", "cachedPassCheckPrefab",
|
||||
|
|
@ -4708,7 +4754,7 @@ export default class JiaZai extends cc.Component {
|
|||
checkAndSetPlayerPassLevel() {
|
||||
const currentLevel = cc.fx.GameConfig.GM_INFO.level;
|
||||
// 判断是否已经记录过playerPassLevel,如果没有并且当前等级大于等于21级,则记录
|
||||
if (currentLevel >= 21 && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
if (currentLevel >= cc.fx.GameConfig.getFeatureUnlockLevel("pass") && cc.fx.GameConfig.GM_INFO.canIos) {
|
||||
const getPassButton = (): cc.Node => {
|
||||
if (!cc.isValid(this, true) || !this.node || !cc.isValid(this.node, true)) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.SceneManager.updateNextMode();
|
||||
}
|
||||
// 记录通行证关卡
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= 21) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= cc.fx.GameConfig.getFeatureUnlockLevel("pass")) {
|
||||
this.setPassProgress();
|
||||
}
|
||||
if (this.new_mode == 3) {
|
||||
|
|
@ -836,7 +836,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.iceNode.parent.getComponent(cc.Button).interactable = false;
|
||||
this.hammerNode.parent.getComponent(cc.Button).interactable = false;
|
||||
this.magicNode.parent.getComponent(cc.Button).interactable = false;
|
||||
} else if (currentLevel >= 8 && currentLevel < 11) {
|
||||
} else if (currentLevel >= 8 && currentLevel < cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
lock0Active = false;
|
||||
lock1Active = true;
|
||||
lock2Active = true;
|
||||
|
|
@ -846,7 +846,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.iceNode.parent.getComponent(cc.Button).interactable = false;
|
||||
this.hammerNode.parent.getComponent(cc.Button).interactable = true;
|
||||
this.magicNode.parent.getComponent(cc.Button).interactable = false;
|
||||
} else if (currentLevel >= 11 && currentLevel < 16) {
|
||||
} else if (currentLevel >= cc.fx.GameConfig.getPropUnlockLevel("time") && currentLevel < cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
lock0Active = false;
|
||||
lock1Active = false;
|
||||
lock2Active = true;
|
||||
|
|
@ -856,7 +856,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.iceNode.parent.getComponent(cc.Button).interactable = true;
|
||||
this.hammerNode.parent.getComponent(cc.Button).interactable = true;
|
||||
this.magicNode.parent.getComponent(cc.Button).interactable = false;
|
||||
} else if (currentLevel >= 16) {
|
||||
} else if (currentLevel >= cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
lock0Active = false;
|
||||
lock1Active = false;
|
||||
lock2Active = false;
|
||||
|
|
@ -871,6 +871,10 @@ export default class MapConroler extends cc.Component {
|
|||
this.itemLock[0].active = lock0Active;
|
||||
this.itemLock[1].active = lock1Active;
|
||||
this.itemLock[2].active = lock2Active;
|
||||
if (cc.fx.GameConfig.isNewPlayerB()) {
|
||||
this.itemLock[1].children[1].getComponent(cc.Sprite).spriteFrame = NumberToImage.font.getSpriteFrame("coin_4");
|
||||
this.itemLock[2].children[1].getComponent(cc.Sprite).spriteFrame = NumberToImage.font.getSpriteFrame("coin_8");
|
||||
}
|
||||
|
||||
if (cc.fx.GameConfig.GM_INFO.magicAFirst == true) {
|
||||
this.magicNode.children[0].active = true;
|
||||
|
|
@ -894,7 +898,8 @@ export default class MapConroler extends cc.Component {
|
|||
public showPropGuideMask(level: number) {
|
||||
const gameInfo = cc.fx.GameConfig.GM_INFO;
|
||||
if (this.propGuideMask || gameInfo.otherLevel != 0 || gameInfo.level + 1 !== level) return;
|
||||
const buttonName = { 8: 'destroyBtn', 11: 'timeBtn', 16: 'magicBtn' }[level];
|
||||
const buttonName = { 8: 'destroyBtn', [cc.fx.GameConfig.getPropUnlockLevel('time')]: 'timeBtn',
|
||||
[cc.fx.GameConfig.getPropUnlockLevel('magic')]: 'magicBtn' }[level];
|
||||
if (!buttonName) return;
|
||||
if (!this.guideMaskPrefab) {
|
||||
cc.error('GuideMask 预制体未绑定');
|
||||
|
|
@ -921,7 +926,7 @@ export default class MapConroler extends cc.Component {
|
|||
const clickEvent = new cc.Component.EventHandler();
|
||||
clickEvent.target = this.node;
|
||||
clickEvent.component = cc.js.getClassName(this);
|
||||
clickEvent.handler = level === 8 ? 'useHammer' : level === 11 ? 'useTimeProp' : 'useMagic';
|
||||
clickEvent.handler = level === 8 ? 'useHammer' : level === cc.fx.GameConfig.getPropUnlockLevel('time') ? 'useTimeProp' : 'useMagic';
|
||||
clickEvent.customEventData = 'guide';
|
||||
button.getComponent(cc.Button).clickEvents = [clickEvent];
|
||||
|
||||
|
|
@ -1007,7 +1012,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.guideNode.getComponent(Animation).stop();
|
||||
this.guideNode.active = false;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.winStreak >= 10) {
|
||||
if (cc.fx.GameConfig.isWinStreakUnlocked() && cc.fx.GameConfig.GM_INFO.winStreak >= 10) {
|
||||
this.is_frenzy = true;
|
||||
}
|
||||
|
||||
|
|
@ -1048,7 +1053,7 @@ export default class MapConroler extends cc.Component {
|
|||
// ============================================
|
||||
initMap() {
|
||||
|
||||
if (cc.fx.GameConfig.GM_INFO.winStreak < 10 || cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
|
||||
if (!cc.fx.GameConfig.isWinStreakUnlocked() || cc.fx.GameConfig.GM_INFO.winStreak < 10 || cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
|
||||
if (this.winStreakBtn.node) {
|
||||
this.winStreakBtn.node.active = false;
|
||||
}
|
||||
|
|
@ -1338,7 +1343,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.guideItem.active = true;
|
||||
this.setPropNum();
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true) {
|
||||
if (cc.fx.GameConfig.GM_INFO.winStreakFirst == true && cc.fx.GameConfig.isWinStreakUnlocked()) {
|
||||
this.guideItem.active = true;
|
||||
}
|
||||
|
||||
|
|
@ -4538,13 +4543,13 @@ export default class MapConroler extends cc.Component {
|
|||
cc.error("playWinSettlement skipped: Win prefab is not ready");
|
||||
return;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.addLevel >= 5) {
|
||||
if (cc.fx.GameConfig.canSettleCareer() && cc.fx.GameConfig.GM_INFO.addLevel >= 5) {
|
||||
this.createRank();
|
||||
}
|
||||
this.createCareer();
|
||||
this.Settlement();
|
||||
const winStreak = winRoot.getChildByName("WinStreak");
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= 17) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level >= cc.fx.GameConfig.getFeatureUnlockLevel("winStreak")) {
|
||||
if (winStreak) winStreak.active = true;
|
||||
this.openWinStreak();
|
||||
}
|
||||
|
|
@ -4748,6 +4753,11 @@ export default class MapConroler extends cc.Component {
|
|||
cc.fx.AudioManager._instance.playEffect("anniu_Big", null);
|
||||
// console.log("下一关", cc.fx.GameConfig.GM_INFO.level);
|
||||
this.uploadToCloud(cc.fx.GameConfig.GM_INFO.level + 1);
|
||||
if (cc.fx.GameConfig.isNewPlayerB() && cc.fx.GameConfig.GM_INFO.level === 15
|
||||
&& !cc.fx.GameConfig.GM_INFO.otherLevel) {
|
||||
this.returnHome(null, null);
|
||||
return;
|
||||
}
|
||||
if (cc.fx.GameTool.maxLevel()) {
|
||||
// 正常游戏到最后一关时 先弹出提示 无尽关暂时都是从外面进入
|
||||
// if (cc.fx.GameConfig.GM_INFO.randomLevel == 0) {
|
||||
|
|
@ -4807,10 +4817,10 @@ export default class MapConroler extends cc.Component {
|
|||
// console.log("新手引导开启");
|
||||
cc.fx.GameConfig.GM_INFO.hammerFirst = true;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 11) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
cc.fx.GameConfig.GM_INFO.freezeFirst = true;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 16) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
cc.fx.GameConfig.GM_INFO.magicAFirst = true;
|
||||
}
|
||||
|
||||
|
|
@ -5290,8 +5300,11 @@ export default class MapConroler extends cc.Component {
|
|||
cc.fx.GameTool.shushu_Track("hammer_frenzy", data2);
|
||||
}
|
||||
// alert(cc.fx.GameConfig.GM_INFO.winStreak);
|
||||
if (cc.fx.GameConfig.GM_INFO.winStreak < 10 || cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
|
||||
if (!cc.fx.GameConfig.isWinStreakUnlocked() || cc.fx.GameConfig.GM_INFO.winStreak < 10 || cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
|
||||
this.hideLoseReasonPanels(lose);
|
||||
if (!cc.fx.GameConfig.isWinStreakUnlocked() && cc.fx.GameConfig.GM_INFO.otherLevel == 0) {
|
||||
cc.fx.GameTool.setWinStreak("fail");
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.revive == 0 && this.revive) {
|
||||
this.revive.getComponent("Revive").offShow();
|
||||
if (this.node) {
|
||||
|
|
@ -5373,10 +5386,10 @@ export default class MapConroler extends cc.Component {
|
|||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 8) {
|
||||
cc.fx.GameConfig.GM_INFO.hammerFirst = true;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 11) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
cc.fx.GameConfig.GM_INFO.freezeFirst = true;
|
||||
}
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == 16) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 == cc.fx.GameConfig.getPropUnlockLevel("magic")) {
|
||||
cc.fx.GameConfig.GM_INFO.magicAFirst = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5702,7 +5715,7 @@ export default class MapConroler extends cc.Component {
|
|||
this.noBlockClickDuration = (currentTime - this.lastBlockClickTime) / 1000; // 转换为秒
|
||||
|
||||
// 如果超过一定时间未点击方块
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 >= 11) {
|
||||
if (cc.fx.GameConfig.GM_INFO.level + 1 >= cc.fx.GameConfig.getPropUnlockLevel("time")) {
|
||||
if (this.noBlockClickDuration >= 5) {
|
||||
let rippleShrink = this.node.parent.getChildByName("Bottom").getChildByName("rippleShrink").getComponent('RippleShrink');
|
||||
if (this.isRippleShrink && rippleShrink && rippleShrink.startRipple) {
|
||||
|
|
@ -7303,6 +7316,13 @@ export default class MapConroler extends cc.Component {
|
|||
const t = (duration: number) => this.settlementAnimationTime(duration);
|
||||
const winRoot = this.getWinRoot();
|
||||
if (!winRoot) return;
|
||||
if (!cc.fx.GameConfig.canSettleCareer()) {
|
||||
["Career", "Rank", "Ruzhi"].forEach(name => {
|
||||
const node = winRoot.getChildByName(name);
|
||||
if (node) node.active = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
let max = false;
|
||||
if (cc.fx.GameConfig.GM_INFO.addLevel == 4) {
|
||||
let address = cc.fx.StorageMessage.getStorage("address");
|
||||
|
|
@ -7324,6 +7344,7 @@ export default class MapConroler extends cc.Component {
|
|||
}
|
||||
winRoot.getChildByName("Rank").active = false;
|
||||
let Career = winRoot.getChildByName("Career");
|
||||
Career.active = true;
|
||||
["bg", "level", "maxlevel", "progress"].forEach((nodeName) => {
|
||||
Career.getChildByName(nodeName).active = false;
|
||||
Career.getChildByName(nodeName + "2").active = true;
|
||||
|
|
@ -7781,6 +7802,7 @@ export default class MapConroler extends cc.Component {
|
|||
// 效果:进入特殊锤子模式,可直接消除任意方块
|
||||
// ============================================
|
||||
useHammerSpecial() {
|
||||
if (!cc.fx.GameConfig.isWinStreakUnlocked()) return;
|
||||
if (this.gameOver == true || this.gameWin == true) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,8 +61,17 @@ export class GameConfig {
|
|||
static LEVEL_INFO: ({ id: string; map: number[]; risefall?: undefined; gap?: undefined; } | { id: string; map: number[]; risefall: { pos: cc.Vec2; color: number; }[]; gap?: undefined; } | { id: string; map: number[]; gap: cc.Vec2[]; risefall?: undefined; })[];
|
||||
static WALL_INFO: (({ id: number; num: number; block: number; color: number; special: number; length: number; lock?: undefined; } | { id: number; num: number; block: number; color: number; special: number; length: number; lock: boolean; })[] | ({ id: number; num: number; block: number; color: number; special: number; length: number; freeze?: undefined; } | { id: number; num: number; block: number; color: number; special: number; length: number; freeze: number; })[])[];
|
||||
static PROP_INFO: ({ pos1: { x: number; y: number; z: number; }; pos2: { x: number; y: number; z: number; }; pos3: { x: number; y: number; z: number; }; pos4: { x: number; y: number; z: number; }; pos5: { x: number; y: number; z: number; }; pos6: { x: number; y: number; z: number; }; } | { pos1: { x: number; y: number; z: number; }; pos2: { x: number; y: number; z: number; }; pos3: { x: number; y: number; z: number; }; pos4: { x: number; y: number; z: number; }; pos5: { x: number; y: number; z: number; }; pos6?: undefined; })[];
|
||||
static NEW_LEVEL: any;
|
||||
static NEW_GUIDE: any;
|
||||
private static defaultNewLevel: any[] = [];
|
||||
private static newPlayerNewLevel: any[] = [];
|
||||
private static defaultNewGuide: any[] = [];
|
||||
static get NEW_LEVEL(): any[] {
|
||||
return this.isNewPlayerB() ? this.newPlayerNewLevel : this.defaultNewLevel;
|
||||
}
|
||||
static get NEW_GUIDE(): any[] {
|
||||
return this.isNewPlayerB()
|
||||
? this.defaultNewGuide.map(item => ({ ...item, level: this.getPropUnlockLevel(item.name) }))
|
||||
: this.defaultNewGuide;
|
||||
}
|
||||
static PASS_CHECK: any;
|
||||
static PASS_CHECK_OLD: any;
|
||||
static PASS_RATE: any;
|
||||
|
|
@ -191,7 +200,7 @@ export class GameConfig {
|
|||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return this.loadLevelFromBundle(levelNumber).then(jsonData => {
|
||||
return this.loadLevelFromBundle(levelNumber, otherLevel === 0).then(jsonData => {
|
||||
if (!isCurrent()) return false;
|
||||
const blockInfo = jsonData && jsonData.BLOCK_INFO && jsonData.BLOCK_INFO[0];
|
||||
const levelInfo = jsonData && jsonData.LEVEL_INFO && jsonData.LEVEL_INFO[0];
|
||||
|
|
@ -392,6 +401,39 @@ export class GameConfig {
|
|||
return this.JUNGLE_TREASURE_UNLOCK_LEVEL;
|
||||
}
|
||||
|
||||
static isNewPlayerB(): boolean {
|
||||
const assignment = this.GM_INFO.abTestAssignments && this.GM_INFO.abTestAssignments.layer_1;
|
||||
return !!assignment && assignment.name === "new1_50" && assignment.enabled !== false
|
||||
&& ["B", "2"].includes(String(assignment.group).toUpperCase());
|
||||
}
|
||||
|
||||
static getLevelConfigPath(level: number, mainline: boolean = true): string {
|
||||
const folder = mainline && this.isNewPlayerB() && level >= 1 && level <= 50 ? "Json2" : "Json";
|
||||
return `${folder}/level${level}`;
|
||||
}
|
||||
|
||||
static getPropUnlockLevel(prop: string): number {
|
||||
if (prop === "hammer") return 8;
|
||||
if (prop === "time") return this.isNewPlayerB() ? 14 : 11;
|
||||
return this.isNewPlayerB() ? 18 : 16;
|
||||
}
|
||||
|
||||
// 以下功能门槛均使用已通关数。
|
||||
static getFeatureUnlockLevel(feature: string): number {
|
||||
const levels = this.isNewPlayerB()
|
||||
? { month: 20, pass: 20, daily: 35, winStreak: 50 }
|
||||
: { month: 16, pass: 21, daily: 0, winStreak: 17 };
|
||||
return levels[feature];
|
||||
}
|
||||
|
||||
static isWinStreakUnlocked(): boolean {
|
||||
return !this.isNewPlayerB() || this.GM_INFO.level >= 50;
|
||||
}
|
||||
|
||||
static canSettleCareer(): boolean {
|
||||
return !this.isNewPlayerB() || this.GM_INFO.level > 15;
|
||||
}
|
||||
|
||||
static TA: any;
|
||||
static GE: any;
|
||||
|
||||
|
|
@ -744,19 +786,26 @@ export class GameConfig {
|
|||
return;
|
||||
}
|
||||
const newLevelData = res.json;
|
||||
this.NEW_LEVEL = newLevelData.NEW_LEVEL;
|
||||
this.defaultNewLevel = newLevelData.NEW_LEVEL;
|
||||
|
||||
// console.log("____________________配置", this.NEW_LEVEL);
|
||||
// 可以根据需求对 newLevelData 进行处理
|
||||
// //console.log("NEW_LEVEL.JSON 加载成功:", newLevelData);
|
||||
})
|
||||
cc.resources.load("Json/NEW_LEVEL2", (err: any, res: cc.JsonAsset) => {
|
||||
if (err) {
|
||||
console.error("加载 NEW_LEVEL2.JSON 失败:", err);
|
||||
return;
|
||||
}
|
||||
this.newPlayerNewLevel = res.json.NEW_LEVEL;
|
||||
});
|
||||
cc.resources.load("Json/NEW_GUIDE", (err: any, res: cc.JsonAsset) => {
|
||||
if (err) {
|
||||
console.error("加载 NEW_GUIDE.JSON 失败:", err);
|
||||
return;
|
||||
}
|
||||
const newLevelData = res.json;
|
||||
this.NEW_GUIDE = newLevelData.NEW_GUIDE;
|
||||
this.defaultNewGuide = newLevelData.NEW_GUIDE;
|
||||
// 可以根据需求对 newLevelData 进行处理
|
||||
// //console.log("NEW_GUIDE.JSON 加载成功:", newLevelData);
|
||||
})
|
||||
|
|
@ -811,6 +860,7 @@ export class GameConfig {
|
|||
if (this.cancelBlockLoading) this.cancelBlockLoading();
|
||||
|
||||
let name = null;
|
||||
let levelNumber: number;
|
||||
if (endless) {
|
||||
let level = 0;
|
||||
if (!cc.fx.GameConfig.GM_INFO.randomLevel || cc.fx.GameConfig.GM_INFO.randomLevel <= 0) {
|
||||
|
|
@ -818,7 +868,8 @@ export class GameConfig {
|
|||
}
|
||||
level = cc.fx.GameConfig.GM_INFO.randomLevel;
|
||||
|
||||
name = "Json/level" + (level);
|
||||
levelNumber = level;
|
||||
name = this.getLevelConfigPath(levelNumber, false);
|
||||
console.log("无尽模式关卡", level);
|
||||
cc.fx.GameConfig.GM_INFO.GameplayType = 1;
|
||||
} else {
|
||||
|
|
@ -829,11 +880,12 @@ export class GameConfig {
|
|||
return;
|
||||
}
|
||||
|
||||
name = "Json/level" + (cc.fx.GameConfig.GM_INFO.level + 1);
|
||||
levelNumber = cc.fx.GameConfig.GM_INFO.level + 1;
|
||||
if (cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
|
||||
console.log("进入特殊帮助关卡,可越过自己关卡等级");
|
||||
name = "Json/level" + cc.fx.GameConfig.GM_INFO.otherLevel;
|
||||
levelNumber = cc.fx.GameConfig.GM_INFO.otherLevel;
|
||||
}
|
||||
name = this.getLevelConfigPath(levelNumber, !cc.fx.GameConfig.GM_INFO.otherLevel);
|
||||
}
|
||||
|
||||
const scene = cc.director.getScene();
|
||||
|
|
@ -877,7 +929,7 @@ export class GameConfig {
|
|||
|
||||
return;
|
||||
}
|
||||
self.applyLevelData(res.json, type, time, Number(name.substring("Json/level".length)), preparation);
|
||||
self.applyLevelData(res.json, type, time, levelNumber, preparation);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1158,10 +1210,10 @@ export class GameConfig {
|
|||
/**
|
||||
* 从本地bundle加载关卡数据
|
||||
*/
|
||||
static loadLevelFromBundle(level: number): Promise<any> {
|
||||
static loadLevelFromBundle(level: number, mainline: boolean = true): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const bundleName = "custom";
|
||||
const name = `Json/level${level}`;
|
||||
const name = this.getLevelConfigPath(level, mainline);
|
||||
|
||||
cc.assetManager.loadBundle(bundleName, { priority: 10 }, (err: Error, bundle: cc.AssetManager.Bundle) => {
|
||||
if (err) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
import cloud from '@lafjs/cloud';
|
||||
const db = cloud.database();
|
||||
export default class Utils {
|
||||
static isNewPlayerB(user: any): boolean {
|
||||
let assignments = user?.abTests;
|
||||
if (typeof assignments === "string") {
|
||||
try { assignments = JSON.parse(assignments); } catch { return false; }
|
||||
}
|
||||
const assignment = assignments?.layer_1;
|
||||
return !!assignment && assignment.name === "new1_50" && assignment.enabled !== false
|
||||
&& ["B", "2"].includes(String(assignment.group).toUpperCase());
|
||||
}
|
||||
|
||||
static checkToken(token, token1) {
|
||||
if (token == token1) {
|
||||
//console.error("校验成功")
|
||||
|
|
|
|||
|
|
@ -52,7 +52,11 @@ export default async function (ctx: FunctionContext) {
|
|||
count = 1;
|
||||
return;
|
||||
}
|
||||
let add = oldAdd + count;
|
||||
// B组前15关只推进主线;补报跨过15关时也只计算门槛之后的胜利。
|
||||
const careerCount = Utils.isNewPlayerB(res1.data)
|
||||
? Math.max(levelAmount - 15, 0) - Math.max(oldlevelAmount - 15, 0)
|
||||
: count;
|
||||
let add = oldAdd + careerCount;
|
||||
let isWhite = res1.data?.isWhite;
|
||||
let careerAmount = res1.data?.careerAmount || 0;
|
||||
if (isWuXian == "true") {
|
||||
|
|
|
|||
46
tools/new-player-ab-test-support.cjs
Normal file
46
tools/new-player-ab-test-support.cjs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const ts = require('typescript');
|
||||
|
||||
function loadMembers(file, names, globals = {}) {
|
||||
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true);
|
||||
const declaration = source.statements.find(ts.isClassDeclaration);
|
||||
const available = declaration ? declaration.members : source.statements.filter(ts.isVariableStatement)
|
||||
.flatMap(statement => statement.declarationList.declarations)
|
||||
.find(node => node.name.getText(source) === 'GameTool').initializer.properties;
|
||||
const members = names.map(name => {
|
||||
const member = available.find(node => node.name && node.name.getText(source) === name);
|
||||
if (!member) throw new Error(`${file}: missing ${name}`);
|
||||
return member.getText(source);
|
||||
});
|
||||
return vm.runInNewContext(ts.transpileModule(`class Controller { ${members.join('\n')} }\nController;`, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2017 },
|
||||
}).outputText, globals);
|
||||
}
|
||||
|
||||
const Config = loadMembers('assets/Script/module/Config/GameConfig.ts', [
|
||||
'isNewPlayerB', 'getLevelConfigPath', 'getPropUnlockLevel', 'getFeatureUnlockLevel',
|
||||
'isWinStreakUnlocked', 'canSettleCareer', 'defaultNewLevel', 'newPlayerNewLevel',
|
||||
'defaultNewGuide', 'NEW_LEVEL', 'NEW_GUIDE',
|
||||
]);
|
||||
function withNewPlayerConfig(config) {
|
||||
for (const name of Object.getOwnPropertyNames(Config)) {
|
||||
if (['length', 'name', 'prototype'].includes(name)) continue;
|
||||
Object.defineProperty(config, name, Object.getOwnPropertyDescriptor(Config, name));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
const ServerUtils = loadMembers('server/laf-cloud/functions/Utils.ts', ['isNewPlayerB']);
|
||||
function loadModule(file, deps = {}, globals = {}) {
|
||||
const exports = {};
|
||||
const code = ts.transpileModule(fs.readFileSync(file, 'utf8'), {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS },
|
||||
}).outputText;
|
||||
vm.runInNewContext(code, { exports, require: name => {
|
||||
if (!(name in deps)) throw new Error(`${file}: missing dependency ${name}`);
|
||||
return deps[name];
|
||||
}, Date, console, ...globals });
|
||||
return exports;
|
||||
}
|
||||
module.exports = { loadMembers, withNewPlayerConfig, ServerUtils, loadModule };
|
||||
|
|
@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
|
|||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const ts = require('typescript');
|
||||
const { withNewPlayerConfig } = require('./new-player-ab-test-support.cjs');
|
||||
const rows = JSON.parse(fs.readFileSync('assets/resources/Json/PASS_CHECK.json', 'utf8')).Sheet1;
|
||||
const quiet = { log() {}, warn() {}, error() {} };
|
||||
function load(file, deps = {}, globals = {}) {
|
||||
|
|
@ -42,6 +43,7 @@ function runtime() {
|
|||
find(path, root) { return path.split('/').reduce((node, name) => node && node.getChildByName(name), root); },
|
||||
fx: { GameConfig: { PASS_CHECK: rows, GM_INFO: {}, isGameplayEntryPreparing: () => false }, GameTool: { adaptation: () => 1, shushu_Track() {} }, StorageMessage: { getStorage: () => 0, setStorage() {} } },
|
||||
Tween: { stopAllByTarget() {} }, tween(target) { const chain = { delay: () => chain, to: (_, values) => { Object.assign(target, values); return chain; }, call: fn => { fn(); return chain; }, start() {} }; return chain; } };
|
||||
withNewPlayerConfig(cc.fx.GameConfig);
|
||||
const child = (parent, name) => { const node = new Node(name); parent.addChild(node); return node; };
|
||||
const numbers = { default: { numberToImageNodesPassCheck(value, _w, _h, _prefix, node) { node.number = value; } } };
|
||||
const Item = load('assets/shop/script/passCheckItem.ts', { '../../Script/NumberToImage': numbers }, { cc }).default;
|
||||
|
|
@ -209,6 +211,18 @@ test('daily intro does not record failed loading or requests and duplicate repli
|
|||
assert.equal(local.size, 1);
|
||||
});
|
||||
|
||||
test('B battle pass waits until clearing 20, then uses the existing daily introduction', () => {
|
||||
const { home, cc, callbacks } = homeFixture();
|
||||
Object.assign(cc.fx.GameConfig.GM_INFO, { level: 19, canIos: true, uid: 'new-player',
|
||||
abTestAssignments: { layer_1: { name: 'new1_50', group: 'B' } } });
|
||||
home.deferHomePopupWhileTransfer = () => false;
|
||||
home.popUpPassCheck(); assert.equal(callbacks.length, 0);
|
||||
cc.fx.GameConfig.GM_INFO.level = 20;
|
||||
home.popUpPassCheck(); assert.equal(callbacks.length, 1);
|
||||
callbacks.shift()({ code: 1, data: { upgradeReady: true, time: Date.now() - 1000, passCheck: 'null' } });
|
||||
assert.equal(home.node.children.filter(n => n.name === 'intro').length, 1);
|
||||
});
|
||||
|
||||
test('cold intro loading survives another home popup request and records only after showing', async () => {
|
||||
const { home } = homeFixture();
|
||||
let ready = false, finishLoad, shown = 0;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ function fixture() {
|
|||
Config.prepareBlockWindow = () => { state.events.push('blocks'); return state.blocks.promise; };
|
||||
Config.warmupHomeBlockAssets = () => state.blocks.promise;
|
||||
Config.showBlockLoadError = (error, retry) => state.errors.push({ error, retry });
|
||||
require('./new-player-ab-test-support.cjs').withNewPlayerConfig(Config);
|
||||
cc.fx = { GameConfig: Config, GameTool };
|
||||
return { state, cc, Config, BlockAssetManager };
|
||||
}
|
||||
|
|
|
|||
231
tools/test-new-player-ab.cjs
Normal file
231
tools/test-new-player-ab.cjs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { loadMembers, withNewPlayerConfig, ServerUtils, loadModule } = require('./new-player-ab-test-support.cjs');
|
||||
const configFile = 'assets/Script/module/Config/GameConfig.ts';
|
||||
const mapFile = 'assets/Script/Map.ts';
|
||||
const homeFile = 'assets/Script/JiaZai.ts';
|
||||
const assignment = group => ({ layer_1: { name: 'new1_50', group, enabled: true } });
|
||||
const config = (level = 0, group = 'B') => withNewPlayerConfig({ GM_INFO: {
|
||||
level, otherLevel: 0, abTestAssignments: assignment(group), canIos: true, addLevel: 0, winStreak: 0,
|
||||
} });
|
||||
const json = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
|
||||
test('front and back agree on B/2; A, missing, disabled and unrelated experiments keep defaults', () => {
|
||||
for (const [raw, expected] of [
|
||||
[assignment('B'), true], [assignment(2), true], [assignment('2'), true],
|
||||
[assignment('A'), false], [assignment(1), false], [assignment('1'), false],
|
||||
[assignment(0), false], [{}, false], [undefined, false],
|
||||
[{ layer_1: { name: 'another', group: 'B' } }, false],
|
||||
[{ layer_1: { name: 'new1_50', group: 'B', enabled: false } }, false],
|
||||
]) {
|
||||
const c = config(); c.GM_INFO.abTestAssignments = raw;
|
||||
assert.equal(c.isNewPlayerB(), expected);
|
||||
assert.equal(ServerUtils.isNewPlayerB({ abTests: raw }), expected);
|
||||
assert.equal(ServerUtils.isNewPlayerB({ abTests: JSON.stringify(raw) }), expected);
|
||||
}
|
||||
assert.equal(ServerUtils.isNewPlayerB({ abTests: 'invalid' }), false);
|
||||
});
|
||||
|
||||
test('B mainline switches at 50/51; A, help and endless keep Json', async () => {
|
||||
const requests = [];
|
||||
const cc = { JsonAsset: 'json', assetManager: {
|
||||
loadBundle(_name, _options, done) { done(null, {}); },
|
||||
loadAny(request, _options, done) { requests.push(request.path); done(null, { json: { path: request.path } }); },
|
||||
} };
|
||||
const Loader = loadMembers(configFile, ['loadLevelFromBundle'], { cc });
|
||||
for (const group of ['A', 'B', 0]) {
|
||||
const c = config(0, group); withNewPlayerConfig(Loader); Loader.GM_INFO = c.GM_INFO;
|
||||
for (const level of [1, 15, 50, 51, 52, 100]) {
|
||||
const expected = group === 'B' && level <= 50 ? 'Json2' : 'Json';
|
||||
assert.equal(c.getLevelConfigPath(level), `${expected}/level${level}`);
|
||||
await Loader.loadLevelFromBundle(level);
|
||||
assert.equal(requests.at(-1), `${expected}/level${level}`);
|
||||
await Loader.loadLevelFromBundle(level, false);
|
||||
assert.equal(requests.at(-1), `Json/level${level}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('both introduction tables select B after login and keep NEW_LEVEL2 beyond 50', () => {
|
||||
const c = config(0, 'A');
|
||||
c.defaultNewLevel = json('assets/resources/Json/NEW_LEVEL.json').NEW_LEVEL;
|
||||
c.newPlayerNewLevel = json('assets/resources/Json/NEW_LEVEL2.json').NEW_LEVEL;
|
||||
c.defaultNewGuide = json('assets/resources/Json/NEW_GUIDE.json').NEW_GUIDE;
|
||||
assert.deepEqual(Array.from(c.NEW_LEVEL.slice(0, 4), row => row.level), [7, 15, 25, 35]);
|
||||
assert.deepEqual(Array.from(c.NEW_GUIDE, row => row.level), [8, 11, 16]);
|
||||
c.GM_INFO.abTestAssignments = assignment('B');
|
||||
for (const level of [0, 49, 50, 100]) {
|
||||
c.GM_INFO.level = level;
|
||||
assert.equal(c.NEW_LEVEL, c.newPlayerNewLevel);
|
||||
assert.deepEqual(Array.from(c.NEW_LEVEL.slice(0, 4), row => row.level), [6, 11, 24, 30]);
|
||||
assert.deepEqual(Array.from(c.NEW_GUIDE, row => row.level), [8, 14, 18]);
|
||||
}
|
||||
});
|
||||
|
||||
class Node {
|
||||
constructor(name) { Object.assign(this, { name, children: [], components: {}, active: true }); }
|
||||
addChild(node) { node.parent = this; this.children.push(node); }
|
||||
getChildByName(name) { return this.children.find(node => node.name === name); }
|
||||
getComponent(type) { return this.components[type]; }
|
||||
}
|
||||
function inflate(data, id) {
|
||||
const raw = data[id], node = new Node(raw._name);
|
||||
node.active = raw._active;
|
||||
for (const ref of raw._components || []) {
|
||||
const component = data[ref.__id__];
|
||||
node.components[component.__type__] = { ...component, string: component._string };
|
||||
}
|
||||
for (const ref of raw._children || []) node.addChild(inflate(data, ref.__id__));
|
||||
return node;
|
||||
}
|
||||
function runtime(c) {
|
||||
const cc = { Button: 'cc.Button', Label: 'cc.Label', Sprite: 'cc.Sprite',
|
||||
fx: { GameConfig: c, StorageMessage: { getStorage: () => ({ authorize: true }) }, GameTool: {} },
|
||||
instantiate(node) { const copy = new Node(node.name); copy.components = structuredClone(node.components); return copy; },
|
||||
tween() { const chain = { to() { return chain; }, delay() { return chain; }, start() {} }; return chain; },
|
||||
};
|
||||
return cc;
|
||||
}
|
||||
|
||||
test('home entries are hidden before 20, locked with labels at 20 and unlock at 35/40/50', () => {
|
||||
const c = config(), cc = runtime(c);
|
||||
const data = json('assets/Scene/HomeScene.fire');
|
||||
const top = inflate(data, data.findIndex(n => n._name === 'Top' && n.__type__ === 'cc.Node'));
|
||||
const Home = loadMembers(homeFile, ['refreshNewPlayerActivityButtons', 'setNewPlayerActivityLock',
|
||||
'setJungleTreasureHomeButtonVisible'], { cc });
|
||||
const home = Object.assign(new Home(), { node: { getChildByName: () => ({ getChildByName: () => top }) },
|
||||
getJungleTreasureHomeButton: () => top.getChildByName('jungle'), refreshHomeActivityEntryLayout() {} });
|
||||
for (const level of [19, 20, 34, 35, 39, 40, 49, 50]) {
|
||||
c.GM_INFO.level = level;
|
||||
home.refreshNewPlayerActivityButtons();
|
||||
if (level >= 40) home.setJungleTreasureHomeButtonVisible(true);
|
||||
for (const [name, unlock] of [['day', 36], ['jungle', 41], ['hammer', 51]]) {
|
||||
const button = top.getChildByName(name), locked = level < unlock - 1;
|
||||
assert.equal(button.active, level >= 20, `${level}: ${name} visible`);
|
||||
assert.equal(button.getComponent(cc.Button).interactable, !locked, `${level}: ${name} clickable`);
|
||||
if (locked) {
|
||||
const label = button.getChildByName(name === 'jungle' ? 'time' : 'unlockLevel');
|
||||
assert.equal(label.getComponent(cc.Label).string, `${unlock}关解锁`);
|
||||
}
|
||||
}
|
||||
}
|
||||
home.setJungleTreasureHomeButtonVisible(false);
|
||||
assert.equal(top.getChildByName('jungle').active, false, 'unlocked activity still follows the original visibility result');
|
||||
});
|
||||
|
||||
test('displaying a locked Jungle entry does not request or start the activity', () => {
|
||||
const c = config(20), cc = runtime(c);
|
||||
c.getJungleTreasureUnlockLevel = () => 40;
|
||||
const Home = loadMembers(homeFile, ['initializeJungleTreasureEntryFromLogin', 'canOpenJungleTreasure'], { cc });
|
||||
let reads = 0;
|
||||
const home = Object.assign(new Home(), { deferHomePopupWhileTransfer: () => false,
|
||||
setJungleTreasureHomeButtonVisible() {}, refreshJungleTreasureHomeRedDot() {},
|
||||
requestJungleTreasureData() { reads++; } });
|
||||
for (const level of [20, 35, 39]) { c.GM_INFO.level = level; home.initializeJungleTreasureEntryFromLogin(); }
|
||||
assert.equal(reads, 0);
|
||||
c.GM_INFO.level = 40; home.initializeJungleTreasureEntryFromLogin(); assert.equal(reads, 1);
|
||||
});
|
||||
|
||||
test('front-end Career is hidden without counting through 15 and reused node appears at 16', () => {
|
||||
const c = config(), cc = runtime(c);
|
||||
const data = json('assets/win/prefab/Win.prefab');
|
||||
const root = inflate(data, data.findIndex(n => n.__type__ === 'cc.Node' && n._children?.some(ref => data[ref.__id__]._name === 'Career')));
|
||||
const Map = loadMembers(mapFile, ['createCareer'], { cc, NumberToImage: { numberToImageNodes6() {} } });
|
||||
const map = Object.assign(new Map(), { getWinRoot: () => root, settlementAnimationTime: value => value });
|
||||
for (const level of [1, 14, 15]) {
|
||||
c.GM_INFO.level = level; map.createCareer();
|
||||
assert.equal(c.GM_INFO.addLevel, 0);
|
||||
for (const name of ['Career', 'Rank', 'Ruzhi']) assert.equal(root.getChildByName(name).active, false);
|
||||
}
|
||||
c.GM_INFO.level = 16; map.createCareer();
|
||||
assert.equal(c.GM_INFO.addLevel, 1);
|
||||
assert.equal(root.getChildByName('Career').active, true);
|
||||
});
|
||||
|
||||
test('server counts only victories after 15, including batches crossing the boundary', async () => {
|
||||
for (const [group, oldLevel, level, expected] of [
|
||||
['B', 0, 1, 0], ['B', 14, 15, 0], ['B', 15, 16, 1], ['B', 13, 18, 3],
|
||||
['B', 15, 20, 5], ['B', 18, 18, 0], ['B', 50, 51, 1], ['A', 14, 15, 1], [0, 0, 1, 1],
|
||||
]) {
|
||||
const user = { _id: 'user', levelAmount: oldLevel, addLevel: 0, abTests: assignment(group), film: 100, isWhite: true, address: '浙江' };
|
||||
let update;
|
||||
const cloud = { database: () => ({ collection: () => ({ where: () => ({ getOne: async () => ({ data: user }),
|
||||
update: async payload => { update = payload; return { updated: 1 }; } }) }) }) };
|
||||
const api = loadModule('server/laf-cloud/functions/userLevel.ts', {
|
||||
'@lafjs/cloud': { default: cloud }, '@/Utils': { default: ServerUtils },
|
||||
ip2region: { default: class { search() { return { province: '浙江省' }; } } },
|
||||
}, { process: { env: { SRANK_ID: 'rank' } } }).default;
|
||||
const result = await api({ body: { uid: 'user', action: 'save', levelAmount: level }, headers: {} });
|
||||
assert.equal(result.code, 1);
|
||||
assert.equal(update.addLevel, expected, `${group}: ${oldLevel} -> ${level}`);
|
||||
assert.equal(update.film, 100 + level - oldLevel, 'film still follows all cleared levels');
|
||||
assert.equal(update.levelAmount, level);
|
||||
}
|
||||
});
|
||||
|
||||
test('seven-day activity uses B=15 while other players keep the configured trigger', () => {
|
||||
const service = loadModule('server/laf-cloud/functions/SignInActivity.ts', {
|
||||
'@lafjs/cloud': { default: { database: () => ({}) } }, '@/Utils': { default: ServerUtils }, crypto: require('node:crypto'),
|
||||
});
|
||||
for (const group of ['A', 'B', 0, '2']) {
|
||||
assert.equal(service.getUserSignInTriggerLevel({ abTests: assignment(group) }, { triggerLevel: 23 }), ['B', '2'].includes(group) ? 15 : 23);
|
||||
}
|
||||
});
|
||||
|
||||
test('B seven-day activity activates at cleared level 15 and retains the original duration', async () => {
|
||||
const activity = json('server/laf-cloud/sign-in-activity-config.json');
|
||||
const cloud = { database: () => ({ collection: () => ({ where: () => ({
|
||||
get: async () => ({ data: [activity] }), update: async () => ({ updated: 1 }),
|
||||
}) }) }) };
|
||||
const service = loadModule('server/laf-cloud/functions/SignInActivity.ts', {
|
||||
'@lafjs/cloud': { default: cloud }, '@/Utils': { default: ServerUtils }, crypto: require('node:crypto'),
|
||||
});
|
||||
for (const [group, level, expected] of [['B', 14, false], ['B', 15, true], ['A', 15, false], ['A', 23, true]]) {
|
||||
const result = await service.activateSignInActivity({ _id: 'player', levelAmount: level, abTests: assignment(group) });
|
||||
assert.equal(result.activated, expected);
|
||||
if (expected) assert.equal(result.state.endAt - result.state.startAt, activity.durationDays * 86400000);
|
||||
}
|
||||
});
|
||||
|
||||
test('only B clearing level 15 routes the next-level button home', () => {
|
||||
for (const [group, level, help, expected] of [['B', 15, 0, 1], ['A', 15, 0, 0], ['B', 16, 0, 0], ['B', 15, 15, 0]]) {
|
||||
const c = config(level, group), cc = runtime(c); c.GM_INFO.otherLevel = help;
|
||||
cc.fx.GameTool.maxLevel = () => false;
|
||||
cc.fx.AudioManager = { _instance: { playEffect() {} } };
|
||||
const Map = loadMembers(mapFile, ['winLevel'], { cc, sp: { Skeleton: 'skeleton' }, setTimeout() {} });
|
||||
const node = { getChildByName() { return node; }, getComponent() { return { setAnimation() {} }; } };
|
||||
node.parent = node;
|
||||
let homes = 0;
|
||||
const button = { _touch: true, setTouch(value) { this._touch = value; } };
|
||||
const map = Object.assign(new Map(), { node, isMapRuntimeAlive: () => true, clearLayerTwoWinCoinFly() {}, uploadToCloud() {},
|
||||
getSettlementNextButton: () => ({ getComponent: () => button }), returnHome() { homes++; } });
|
||||
map.winLevel(); assert.equal(homes, expected);
|
||||
}
|
||||
});
|
||||
|
||||
test('B special hammer remains unusable until level 51 despite ten accumulated wins', () => {
|
||||
const c = config(49), cc = runtime(c); c.GM_INFO.winStreak = 10;
|
||||
const Map = loadMembers(mapFile, ['useHammerSpecial'], { cc });
|
||||
let checks = 0;
|
||||
const map = new Map();
|
||||
Object.defineProperty(map, 'gameOver', { get() { checks++; return true; } });
|
||||
map.useHammerSpecial(); assert.equal(checks, 0);
|
||||
c.GM_INFO.level = 50; map.useHammerSpecial(); assert.equal(checks, 1);
|
||||
});
|
||||
|
||||
test('10-win progress and failure reset retain their original behavior before unlock', () => {
|
||||
const c = config(30), cc = runtime(c);
|
||||
cc.fx.StorageMessage.setStorage = () => {};
|
||||
const Tool = loadMembers('assets/Script/module/Tool/GameTool.ts', ['setWinStreak'], { cc, Utils: { setWinStreak() {} } });
|
||||
const tool = new Tool();
|
||||
for (let i = 0; i < 10; i++) tool.setWinStreak('sucess');
|
||||
assert.equal(c.GM_INFO.winStreak, 10);
|
||||
assert.equal(c.GM_INFO.winStreakFirst, true);
|
||||
assert.equal(c.isWinStreakUnlocked(), false);
|
||||
c.GM_INFO.level = 50;
|
||||
assert.equal(c.isWinStreakUnlocked(), true);
|
||||
c.GM_INFO.level = 30;
|
||||
tool.setWinStreak('fail');
|
||||
assert.equal(c.GM_INFO.winStreak, 0);
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ const fs = require('node:fs');
|
|||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const ts = require('typescript');
|
||||
const { withNewPlayerConfig } = require('./new-player-ab-test-support.cjs');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const read = file => JSON.parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
||||
|
||||
|
|
@ -66,12 +67,13 @@ function inflate(data, id) {
|
|||
return node;
|
||||
}
|
||||
|
||||
function fixture(level) {
|
||||
function fixture(level, group = 'A') {
|
||||
const prefab = read('assets/prefab/pop/GuideMask.prefab');
|
||||
const scene = read('assets/Scene/GameScene.fire');
|
||||
const gameNode = inflate(scene, scene.findIndex(node => node._name === 'GameNode'));
|
||||
const info = { level: level - 1, otherLevel: 0, hammerAmount: 3, freezeAmount: 3, magicAmount: 3,
|
||||
tasks: { useProp: { value: 0, target: 0 } } };
|
||||
info.abTestAssignments = { layer_1: { name: 'new1_50', group } };
|
||||
const cc = { Node, Button: 'cc.Button', Widget: 'cc.Widget', Sprite: 'cc.Sprite',
|
||||
Component: { EventHandler: class {} }, js: { getClassName: () => 'MapConroler' },
|
||||
error() {}, isValid: value => !!value && !value.destroyed, instantiate: () => inflate(prefab, 1),
|
||||
|
|
@ -79,6 +81,7 @@ function fixture(level) {
|
|||
GameTool: { setUserProp() {}, shushu_Track() {} },
|
||||
AudioManager: { _instance: { playEffect() {} } } },
|
||||
};
|
||||
withNewPlayerConfig(cc.fx.GameConfig);
|
||||
const Map = loadClass('assets/Script/Map.ts', ['propGuideMask', 'guideHammerPending', 'showPropGuideMask', 'blockPropGuideTouch',
|
||||
'setPropGuideTouchBlocked', 'destroyPropGuideMask', 'useHammer', 'cancleHammer', 'costHammer', 'useTimeProp', 'useMagic'], {
|
||||
cc, MiniGameSdk: { API: { showToast() {} } }, NumberToImage: { getTimeMargi3() {} }, sp: { Skeleton: 'sp.Skeleton' },
|
||||
|
|
@ -203,6 +206,19 @@ test('ordinary hammer is cancellable even on level 8', () => {
|
|||
assert.equal(info.hammerAmount, 3);
|
||||
});
|
||||
|
||||
test('B guides move to 8/14/18 with the matching prop button', () => {
|
||||
for (const [level, name, handler] of [[8, 'destroyBtn', 'useHammer'], [14, 'timeBtn', 'useTimeProp'], [18, 'magicBtn', 'useMagic']]) {
|
||||
const { map } = fixture(level, 'B');
|
||||
map.showPropGuideMask(level);
|
||||
const button = map.propGuideMask.getChildByName('bottom').getChildByName(name);
|
||||
assert.equal(button.active, true);
|
||||
assert.equal(button.getComponent('cc.Button').clickEvents[0].handler, handler);
|
||||
}
|
||||
for (const level of [11, 16]) {
|
||||
const { map } = fixture(level, 'B'); map.showPropGuideMask(level); assert.equal(map.propGuideMask, null);
|
||||
}
|
||||
});
|
||||
|
||||
test('ordinary levels, stale callbacks and friend levels do not show guide', () => {
|
||||
for (const level of [1, 7, 9, 10, 12, 15, 17]) {
|
||||
const { map } = fixture(level); map.showPropGuideMask(level); assert.equal(map.propGuideMask, null);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user