🎨 PVE文件夹重构,根据BOSS文件进行分包

📝 新增术樱包武将
This commit is contained in:
2026-08-11 15:59:31 +08:00
parent 6735fc2d35
commit 690e55736d
47 changed files with 4439 additions and 2725 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
const characters = {
zuoci_shuying: {
sex: "male",
group: "qun",
hp: 3,
skills: ["huashen_shuying", "xinsheng_shuying", "jihun_shuying"],
img: "extension/术樱包/character/images/zuoci_shuying.jpg",
dieAudios: ["ext:术樱包/character/audio/zuoci/zuoci_shuying.mp3"]
},
};
export default characters;
+3
View File
@@ -0,0 +1,3 @@
const characterFilters = {};
export default characterFilters;
+3
View File
@@ -0,0 +1,3 @@
const characterTitles = {};
export default characterTitles;
+3
View File
@@ -0,0 +1,3 @@
const dynamicTranslates = {};
export default dynamicTranslates;
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+28
View File
@@ -0,0 +1,28 @@
import characters from "./character.js";
import skills from "./skill.js";
import translates from "./translate.js";
import characterFilters from "./characterFilter.js";
import characterTitles from "./characterTitle.js";
import characterIntros from "./intro.js";
import dynamicTranslates from "./dynamicTranslate.js";
import perfectPairs from "./perfectPairs.js";
import voices from "./voices.js";
import { characterSort, characterSortTranslate } from "./sort.js";
const shuying_character = {
name: "shuYing_character",
connect: true,
character: { ...characters },
characterSort: {
shuYing_character: characterSort,
},
characterFilter: { ...characterFilters },
characterTitle: { ...characterTitles },
dynamicTranslate: { ...dynamicTranslates },
characterIntro: { ...characterIntros },
skill: { ...skills },
perfectPair: { ...perfectPairs },
translate: { ...translates, ...voices, ...characterSortTranslate },
};
export default shuying_character;
+3
View File
@@ -0,0 +1,3 @@
const characterIntros = {};
export default characterIntros;
+3
View File
@@ -0,0 +1,3 @@
const perfectPairs = {};
export default perfectPairs;
+388
View File
@@ -0,0 +1,388 @@
import { lib, game, ui, get, ai, _status } from "../../../noname.js";
const skills = {
huashen_shuying: {
audio: "ext:术樱包/character/audio/zuoci:2",
inherit: "rehuashen",
unique: true,
init(player, skill) {
if (!player.storage.rehuashen) {
player.storage.rehuashen = {
character: [],
choosed: [],
map: {},
};
}
player.storage[skill] = player.storage.rehuashen;
},
async content(event, trigger, player) {
const oldCharacter = player.storage.rehuashen?.current;
const oldSkill = player.storage.rehuashen?.current2;
const transitionKey = "jihun_shuying_transition";
// 单独处理重铸,保留本次实际移除的化身牌信息
if (event.cost_data === "制衡其他化身") {
_status.noclearcountdown = true;
const id = lib.status.videoId++;
const prompt = "化身:选择制衡至多两张武将牌";
const cards = player.storage.rehuashen.character;
if (player.isOnline2()) {
player.send(
(cards, prompt, id) => {
const dialog = ui.create.dialog(
prompt,
[cards, lib.skill.rehuashen.$createButton]
);
dialog.videoId = id;
},
cards,
prompt,
id
);
}
const dialog = ui.create.dialog(
prompt,
[cards, lib.skill.rehuashen.$createButton]
);
dialog.videoId = id;
if (!event.isMine()) {
dialog.style.display = "none";
}
const result = await player
.chooseButton(true)
.set("dialog", id)
.set("selectButton", [1, 2])
.set(
"filterButton",
button => button.link !== get.event().current
)
.set("current", player.storage.rehuashen.current)
.forResult();
if (player.isOnline2()) {
player.send("closeDialog", id);
}
dialog.close();
delete _status.noclearcountdown;
if (!_status.noclearcountdown) {
game.stopCountChoose();
}
const removedCharacters = result.links || [];
lib.skill.rehuashen.removeHuashen(
player,
removedCharacters
);
if (
player.storage.jihun_shuying_retainedCharacter &&
removedCharacters.includes(
player.storage.jihun_shuying_retainedCharacter
)
) {
delete player.storage.jihun_shuying_retained;
delete player.storage.jihun_shuying_retainedCharacter;
delete player.storage.jihun_shuying_ignoredEvents;
await player.removeAdditionalSkills(
"jihun_shuying_retained"
);
}
lib.skill.rehuashen.addHuashens(
player,
removedCharacters.length
);
player.storage.huashen_shuying =
player.storage.rehuashen;
player.syncStorage("huashen_shuying");
player.updateMarks("huashen_shuying");
return;
}
// 切换前为当前技能增加临时所有权。官方化身移除旧来源时,
// getRemovableAdditionalSkills 会因此保留技能本体及全部状态。
const protectOldSkill = Boolean(
oldSkill && player.hasSkill("jihun_shuying")
);
if (
protectOldSkill &&
!player.additionalSkills[transitionKey]?.includes(oldSkill)
) {
if (!Array.isArray(player.additionalSkills[transitionKey])) {
player.additionalSkills[transitionKey] = [];
}
player.additionalSkills[transitionKey].push(oldSkill);
game.broadcast(
(player, map) => {
player.additionalSkills = map;
},
player,
player.additionalSkills
);
_status.event.clearStepCache();
}
await lib.skill.rehuashen.content(event, trigger, player);
const storage = player.storage.rehuashen;
const changed = Boolean(
oldSkill &&
(
oldCharacter !== storage?.current ||
oldSkill !== storage?.current2
)
);
if (
changed &&
player.hasSkill("jihun_shuying")
) {
// 过渡来源会阻止官方流程物理移除旧技能,因此这里仅删除
// rehuashen 对旧技能的映射,不触发 removeSkill/onremove。
if (
oldSkill !== storage.current2 &&
player.additionalSkills.rehuashen?.includes(oldSkill)
) {
player.$removeAdditionalSkills("rehuashen", oldSkill);
_status.event.clearStepCache();
}
// 新的当前技能已经登记完毕后,再结束上一次羁魂保留。
if (player.additionalSkills.jihun_shuying_retained) {
await player.removeAdditionalSkills(
"jihun_shuying_retained"
);
}
delete player.storage.jihun_shuying_retained;
delete player.storage.jihun_shuying_retainedCharacter;
delete player.storage.jihun_shuying_ignoredEvents;
player.logSkill("jihun_shuying");
if (oldSkill && oldSkill !== storage.current2) {
const ignoredEvents = player
.getAllHistory("useSkill", history => {
return get.sourceSkillFor(history) === oldSkill;
})
.map(history => history.event)
.filter(Boolean);
let currentEvent = _status.event;
while (currentEvent) {
const eventSkill = currentEvent.sourceSkill ||
currentEvent.skill ||
(lib.skill[currentEvent.name] ? currentEvent.name : null);
if (
eventSkill &&
get.sourceSkillFor(eventSkill) === oldSkill &&
!ignoredEvents.includes(currentEvent)
) {
ignoredEvents.push(currentEvent);
}
currentEvent = currentEvent.parent;
}
player.storage.jihun_shuying_retained = oldSkill;
player.storage.jihun_shuying_retainedCharacter =
oldCharacter;
player.storage.jihun_shuying_ignoredEvents = ignoredEvents;
// 此时 oldSkill 仍由过渡键持有,增加羁魂来源不会重新
// 初始化技能,也不会影响牌、标记和 storage。
const retainedKey = "jihun_shuying_retained";
if (!Array.isArray(player.additionalSkills[retainedKey])) {
player.additionalSkills[retainedKey] = [];
}
if (!player.additionalSkills[retainedKey].includes(oldSkill)) {
player.additionalSkills[retainedKey].push(oldSkill);
}
game.broadcast(
(player, map) => {
player.additionalSkills = map;
},
player,
player.additionalSkills
);
_status.event.clearStepCache();
}
}
// 最终所有权建立完毕后再撤销过渡来源。
if (player.additionalSkills[transitionKey]) {
await player.removeAdditionalSkills(transitionKey);
}
player.storage.huashen_shuying = storage;
player.syncStorage("huashen_shuying");
player.updateMarks("huashen_shuying");
},
onremove(player) {
player.removeAdditionalSkill("rehuashen");
player.removeAdditionalSkill("jihun_shuying_retained");
player.removeAdditionalSkill("jihun_shuying_transition");
delete player.storage.rehuashen;
delete player.storage.huashen_shuying;
delete player.storage.jihun_shuying_retained;
delete player.storage.jihun_shuying_retainedCharacter;
delete player.storage.jihun_shuying_ignoredEvents;
},
ai: {
threaten: 2,
},
},
xinsheng_shuying: {
audio: "ext:术樱包/character/audio/zuoci:2",
unique: true,
trigger: {
player: "damageEnd",
},
filter(event, player) {
return event.num > 0 &&
player.hasSkill("huashen_shuying") &&
player.storage.rehuashen;
},
async cost(event, trigger, player) {
const controls = ["获得一张新化身"];
if (player.storage.rehuashen.character?.length) {
controls.push("更换所展示的化身");
}
controls.push("cancel2");
const result = await player
.chooseControl(controls)
.set("prompt", get.prompt(event.skill))
.set("prompt2", "获得一张新化身牌,或更换所展示的化身牌")
.set("ai", () => {
const player = get.player();
const controls = get.event().controls;
if (!controls.includes("更换所展示的化身")) {
return "获得一张新化身";
}
const storage = player.storage.rehuashen;
const currentRank = storage.current2 ?
get.skillRank(storage.current2, "in") : 0;
const skills = storage.character
.flatMap(name => storage.map[name] || [])
.unique();
const bestRank = skills.reduce((max, skill) => {
return Math.max(max, get.skillRank(skill, "in"));
}, 0);
if (bestRank > currentRank + 1) {
return "更换所展示的化身";
}
return "获得一张新化身";
})
.forResult();
event.result = {
bool: result.control !== "cancel2",
cost_data: result.control,
};
},
async content(event, trigger, player) {
if (event.cost_data === "获得一张新化身") {
lib.skill.rehuashen.addHuashens(player, 1);
player.storage.huashen_shuying = player.storage.rehuashen;
player.syncStorage("huashen_shuying");
player.updateMarks("huashen_shuying");
return;
}
const next = game.createEvent("huashen_shuying_change");
next.player = player;
next.skill = "huashen_shuying";
next.cost_data = "替换当前化身";
next.setContent(lib.skill.huashen_shuying.content);
await next;
},
ai: {
combo: "huashen_shuying",
maixie: true,
maixie_hp: true,
effect: {
target(card, player, target) {
if (
get.tag(card, "damage") &&
target.hasSkill("huashen_shuying")
) {
return [1, 0.7];
}
},
},
},
},
jihun_shuying: {
audio: "ext:术樱包/character/audio/zuoci:2",
locked: true,
group: "jihun_shuying_clear",
onremove(player) {
player.removeAdditionalSkill("jihun_shuying_retained");
player.removeAdditionalSkill("jihun_shuying_transition");
delete player.storage.jihun_shuying_retained;
delete player.storage.jihun_shuying_retainedCharacter;
delete player.storage.jihun_shuying_ignoredEvents;
},
ai: {
combo: "huashen_shuying",
},
subSkill: {
clear: {
charlotte: true,
silent: true,
lastDo: true,
priority: -Infinity,
trigger: {
player: [
"useSkillAfter",
"useCardAfter",
"respondAfter",
"triggerAfter",
"skillAfter",
],
},
hookTrigger: {
after(event, player) {
const retained =
player.storage.jihun_shuying_retained;
return Boolean(
retained &&
event.skill &&
get.sourceSkillFor(event.skill) === retained
);
},
},
filter(event, player) {
const retained =
player.storage.jihun_shuying_retained;
if (
!retained ||
!event.skill ||
get.sourceSkillFor(event.skill) !== retained
) {
return false;
}
const ignoredEvents =
player.storage.jihun_shuying_ignoredEvents || [];
return !ignoredEvents.includes(event);
},
async content(event, trigger, player) {
delete player.storage.jihun_shuying_retained;
delete player.storage.jihun_shuying_retainedCharacter;
delete player.storage.jihun_shuying_ignoredEvents;
await player.removeAdditionalSkills(
"jihun_shuying_retained"
);
},
},
},
},
};
export default skills;
+4
View File
@@ -0,0 +1,4 @@
const characterSort = {};
const characterSortTranslate = {};
export { characterSort, characterSortTranslate };
+11
View File
@@ -0,0 +1,11 @@
const translates = {
zuoci_shuying: "术左慈",
huashen_shuying: "化身",
huashen_shuying_info: "游戏开始时,你随机获得三张未加入游戏的武将牌,选一张置于你面前并声明该武将的一项技能,你拥有该技能且同时将性别和势力属性变为与该武将相同,直到该化身被替换。回合开始时或结束时,你可以选择一项:①弃置至多两张未展示的化身并重新获得等量化身;②更换所展示的化身牌或技能。(你不可以声明限定技、觉醒技、隐匿技、使命技、主公技等特殊技能)",
xinsheng_shuying: "新生",
xinsheng_shuying_info: "若你拥有技能〖化身〗,当你受到伤害后,你可以选择一项:①获得一张新化身牌;②更换所展示的化身牌。",
jihun_shuying: "羁魂",
jihun_shuying_info: "若你拥有技能〖化身〗,当你更换所展示的化身牌或技能时,保留上次声明的技能,直至该技能下次触发、再次更换所展示的化身牌或技能、重铸该技能对应的化身牌。",
};
export default translates;
+3
View File
@@ -0,0 +1,3 @@
const voices = {};
export default voices;
+7 -6
View File
@@ -223,13 +223,10 @@ shuYing.initCharacter = async () => {
// "yuanshen",
// ];
}
else {
// initBengTie(lib, game, ui, get, ai, _status, shuYing);
// shuYingList =
// [
// "yuanshen",
// ];
const shuying_character = await shuYing.loadModule("./character/index.js", "术樱包", true);
if (shuying_character) {
shuYing.appendExtension("shuYing_character", "术樱包", shuying_character);
}
// shuYingList.forEach(url => {
@@ -247,6 +244,10 @@ shuYing.precontent = async () => {
shuYing.initCSS();
shuYing.initFunction();
shuYing.initConfig();
// const initMengsan = await shuYing.loadModule("./mengsan/mode.js", "梦三模式", true);
// if (typeof initMengsan == "function") {
// await initMengsan();
// }
initShuYingMenu({ lib, game, ui, shuYing, updater });
shuYing.getVersion();
await shuYing.initCharacter();
+68
View File
@@ -0,0 +1,68 @@
const mengsanConfig = {
modeId: "mengsan_shuying",
saveKey: "mengsan_run_shuying",
profileKey: "mengsan_profile_shuying",
schemaVersion: 1,
mapLayoutVersion: 3,
routeCount: 4,
characters: ["re_zhaoyun", "sunquan", "re_huangyueying", "re_zhangfei"],
acts: [
{
id: "act1",
name: "第一关·初梦",
floorNodes: [1, 4, 4, 4, 1],
minFloorNodes: [1, 3, 3, 3, 1],
nodeWeights: { battle: 65, event: 15, rest: 10, shop: 10 },
enemies: ["re_xiahoudun", "re_zhangliao", "re_xuzhu"],
boss: "re_lvbu",
baseGold: 20,
},
{
id: "act2",
name: "第二关·迷梦",
floorNodes: [1, 4, 4, 4, 4, 1],
minFloorNodes: [1, 3, 2, 3, 3, 1],
nodeWeights: { battle: 55, elite: 15, event: 12, rest: 9, shop: 9 },
enemies: ["re_ganning", "re_zhangfei", "re_huangzhong", "re_weiyan"],
eliteEnemies: ["re_guanyu", "dianwei"],
boss: "shen_guanyu",
baseGold: 30,
},
{
id: "act3",
name: "第三关·终梦",
floorNodes: [1, 4, 4, 4, 4, 4, 1],
minFloorNodes: [1, 3, 2, 3, 2, 3, 1],
nodeWeights: { battle: 45, elite: 20, event: 15, rest: 10, shop: 10 },
enemies: ["re_simayi", "re_sunben", "dongzhuo", "re_yuanshao"],
eliteEnemies: ["shen_zhaoyun", "shen_simayi"],
boss: "shen_caocao",
baseGold: 40,
},
],
startingDeck: [
["spade", 7, "sha"],
["heart", 10, "sha"],
["club", 4, "sha"],
["diamond", 6, "sha"],
["heart", 2, "shan"],
["diamond", 7, "shan"],
["club", 2, "shan"],
["heart", 3, "tao"],
["spade", 3, "guohe"],
["heart", 7, "wuzhong"],
["spade", 2, "bagua"],
["club", 6, "sha", null, ["annihilate"]],
],
nodeNames: {
battle: "战斗",
elite: "精英",
event: "事件",
story: "剧情战斗",
rest: "休息",
shop: "商店",
boss: "Boss",
},
};
export default mengsanConfig;
+540
View File
@@ -0,0 +1,540 @@
import { lib, game, ui, get, _status } from "../../../noname.js";
import config from "./config.js";
import {
completeNode,
createRun,
enterNextAct,
generateActMap,
getNodeEncounter,
getSelectableNodes,
insertStoryNode,
nextRandom,
} from "./state.js";
const MODE_ID = config.modeId;
const STYLE_PATH = `${lib.assetURL}extension/术樱包/mengsan`;
const copy = value => JSON.parse(JSON.stringify(value));
const persistModeStorage = async () => {
lib.storage.version = lib.version;
if (lib.db) {
await game.putDB("data", MODE_ID, copy(lib.storage));
}
else {
localStorage.setItem(`${lib.configprefix}${MODE_ID}`, JSON.stringify(lib.storage));
}
};
const saveRun = async run => {
try {
lib.storage[config.saveKey] = copy(run);
await persistModeStorage();
}
catch (error) {
delete lib.storage[config.saveKey];
try {
await persistModeStorage();
}
catch {}
alert("梦三存档失败,当前征程存档已清空。请查看控制台错误。");
console.error("梦三存档失败:", error);
throw error;
}
};
const clearRun = async () => {
delete lib.storage[config.saveKey];
await persistModeStorage();
};
const createOverlay = (title, className = "") => {
const overlay = ui.create.div(`.mengsan-overlay-shuying${className ? `.${className}` : ""}`, ui.window);
const panel = ui.create.div(".mengsan-panel-shuying", overlay);
ui.create.div(".mengsan-title-shuying", title, panel);
return { overlay, panel };
};
const chooseButtons = (title, choices, description = "") => new Promise(resolve => {
const { overlay, panel } = createOverlay(title);
if (description) ui.create.div(".mengsan-description-shuying", description, panel);
const buttons = ui.create.div(".mengsan-choice-list-shuying", panel);
choices.forEach(choice => {
const button = ui.create.div(".mengsan-choice-shuying", buttons);
button.innerHTML = `<div class="mengsan-choice-name-shuying">${choice.name}</div>${choice.description ? `<div class="mengsan-choice-description-shuying">${choice.description}</div>` : ""}`;
if (choice.disabled) button.classList.add("disabled");
button.addEventListener(lib.config.touchscreen ? "touchend" : "click", event => {
event.preventDefault();
if (choice.disabled || overlay.dataset.resolved) return;
overlay.dataset.resolved = "true";
overlay.remove();
resolve(choice.id);
});
});
});
const chooseRun = async savedRun => {
if (!savedRun || savedRun.status != "running") return "new";
const act = config.acts[savedRun.actIndex];
return chooseButtons("梦三", [
{
id: "continue",
name: "继续征程",
description: `${act?.name || "未知区域"} · 已通过 ${savedRun.statistics?.completedNodes || 0} 个节点`,
},
{
id: "new",
name: "新的开始",
description: "覆盖当前征程并重新生成第一关地图",
},
], "Demo 采用单一自动存档。节点完成后才会覆盖检查点。" );
};
const chooseCharacter = async () => {
const choices = config.characters.filter(name => lib.character[name]).map(name => ({
id: name,
name: get.translation(name),
description: lib.translate[`${name}_title`] || "作为本次征程的角色",
}));
return chooseButtons("选择角色", choices.length ? choices : [{ id: "re_zhaoyun", name: "赵云" }]);
};
const nodeColor = {
battle: "#9d4937",
elite: "#7e3f79",
event: "#477aa6",
story: "#8c6a34",
rest: "#3f8560",
shop: "#a27b35",
boss: "#a62727",
};
const showMap = run => new Promise(resolve => {
const act = config.acts[run.actIndex];
const selectable = getSelectableNodes(run);
const selectableIds = new Set(selectable.map(node => node.id));
const { overlay, panel } = createOverlay(act.name, "mengsan-map-overlay-shuying");
panel.classList.add("mengsan-map-panel-shuying");
const status = ui.create.div(".mengsan-map-status-shuying", panel);
status.innerHTML = `体力:${run.player.hp == null ? "未初始化" : `${run.player.hp}/${run.player.maxHp}`} 金币:${run.player.gold} 牌组:${run.player.deck.length}`;
const map = ui.create.div(".mengsan-map-shuying", panel);
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add("mengsan-lines-shuying");
svg.setAttribute("viewBox", "0 0 100 100");
svg.setAttribute("preserveAspectRatio", "none");
svg.setAttribute("aria-hidden", "true");
map.appendChild(svg);
const nodeMap = new Map(run.map.nodes.map(node => [node.id, node]));
run.map.edges.forEach(edge => {
const source = nodeMap.get(edge[0]);
const target = nodeMap.get(edge[1]);
if (!source || !target) return;
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", source.x);
line.setAttribute("y1", source.y);
line.setAttribute("x2", target.x);
line.setAttribute("y2", target.y);
if (source.completed) line.classList.add("completed");
if (source.dynamic || target.dynamic) line.classList.add("dynamic");
svg.appendChild(line);
});
run.map.nodes.forEach(node => {
const button = ui.create.div(".mengsan-node-shuying", map);
button.style.left = `${node.x}%`;
button.style.top = `${node.y}%`;
button.style.setProperty("--node-color", nodeColor[node.type] || "#666");
button.innerHTML = `<span>${config.nodeNames[node.type] || node.type}</span>`;
button.dataset.nodeId = node.id;
if (node.completed) button.classList.add("completed");
else if (selectableIds.has(node.id)) button.classList.add("selectable");
else button.classList.add("locked");
button.addEventListener(lib.config.touchscreen ? "touchend" : "click", event => {
event.preventDefault();
if (!selectableIds.has(node.id) || overlay.dataset.resolved) return;
overlay.dataset.resolved = "true";
overlay.remove();
resolve(node);
});
});
});
const createCardInstance = (run, name = "sha", upgrade = 0, affixes = []) => ({
id: `mengsan_card_${Date.now()}_${Math.floor(nextRandom(run) * 1e6)}`,
suit: ["spade", "heart", "club", "diamond"][Math.floor(nextRandom(run) * 4)],
number: 1 + Math.floor(nextRandom(run) * 13),
name,
nature: null,
upgrade,
affixes: affixes.slice(),
});
const applyReward = (run, rewardId) => {
const player = run.player;
switch (rewardId) {
case "card_sha":
player.deck.push(createCardInstance(run, "sha"));
break;
case "card_tao":
player.deck.push(createCardInstance(run, "tao"));
break;
case "upgrade": {
const card = player.deck[Math.floor(nextRandom(run) * player.deck.length)];
if (card) card.upgrade = Number(card.upgrade || 0) + 1;
break;
}
case "heal":
player.hp = Math.min(player.maxHp, player.hp + 2);
break;
case "max_hp":
player.maxHp++;
player.hp++;
break;
case "item_hand":
if (!player.items.includes("mengsan_hand_charm_shuying")) {
player.items.push("mengsan_hand_charm_shuying");
player.handLimitBonus++;
}
break;
case "skill_yingyong":
if (!player.permanentSkills.includes("mengsan_yingyong_shuying")) {
player.permanentSkills.push("mengsan_yingyong_shuying");
}
break;
}
};
const chooseBattleReward = async (run, boss) => {
const choices = boss ? [
{ id: "skill_yingyong", name: "极品技能·英勇", description: "本次征程永久获得【英勇】" },
{ id: "item_hand", name: "极品道具·束带", description: "基础手牌上限额外 +1" },
{ id: "max_hp", name: "极品强化·生机", description: "体力上限与当前体力各 +1" },
] : [
{ id: "card_sha", name: "获得一张【杀】", description: "加入独立永久牌组" },
{ id: "card_tao", name: "获得一张【桃】", description: "加入独立永久牌组" },
{ id: "upgrade", name: "随机强化", description: "随机一张牌强化一级" },
];
const reward = await chooseButtons(boss ? "极品奖励(三选一)" : "战斗奖励(三选一)", choices);
applyReward(run, reward);
};
const finishUtilityNode = async (run, node) => {
if (node.type == "rest") {
const choice = await chooseButtons("休息节点", [
{ id: "heal", name: "休息", description: "回复 2 点体力" },
{ id: "upgrade", name: "磨砺", description: "随机强化一张卡牌" },
]);
applyReward(run, choice);
}
else if (node.type == "shop") {
const price = 20;
const choice = await chooseButtons("商店 Demo", [
{ id: "card_sha", name: `购买【杀】(${price}金币)`, description: "加入个人牌组", disabled: run.player.gold < price },
{ id: "heal", name: `恢复体力(${price}金币)`, description: "回复 2 点体力", disabled: run.player.gold < price },
{ id: "leave", name: "离开", description: "暂不购买" },
]);
if (choice != "leave") {
run.player.gold -= price;
applyReward(run, choice);
}
}
else {
const choice = await chooseButtons("随机事件", [
{ id: "branch", name: "调查异象", description: "在当前路线后插入一个额外剧情战斗节点" },
{ id: "gold", name: "收下钱袋", description: "获得 15 金币" },
]);
if (choice == "branch") insertStoryNode(run, node.id);
else {
run.player.gold += 15;
run.statistics.goldEarned += 15;
}
}
completeNode(run, node.id);
await saveRun(run);
game.reload();
};
const shuffleBattlePile = (run, cards) => {
for (let index = cards.length - 1; index > 0; index--) {
const target = Math.floor(nextRandom(run) * (index + 1));
[cards[index], cards[target]] = [cards[target], cards[index]];
}
};
const setupBattle = async (run, node) => {
const encounter = getNodeEncounter(run, node);
_status.mengsanRun_shuying = run;
_status.mengsanNode_shuying = node;
_status.mengsanEncounter_shuying = encounter;
_status.mengsanBattle_shuying = {
resolving: false,
drawPile: copy(run.player.deck),
discardPile: [],
exhaustPile: [],
};
shuffleBattlePile(run, _status.mengsanBattle_shuying.drawPile);
game.prepareArena(2);
const me = game.me;
const enemy = game.players.find(current => current != me);
me.init(run.player.character);
enemy.init(lib.character[encounter.enemy] ? encounter.enemy : "re_lvbu");
me.identity = "zhu";
enemy.identity = "fan";
me.side = false;
enemy.side = true;
me.setIdentity("zhu");
enemy.setIdentity("fan");
me.ai.shown = 1;
enemy.ai.shown = 1;
me.storage.mengsanPlayer_shuying = true;
game.zhu = me;
if (run.player.maxHp == null) {
run.player.maxHp = me.maxHp;
run.player.hp = me.hp;
}
me.maxHp = run.player.maxHp;
me.hp = Math.max(1, Math.min(run.player.hp, me.maxHp));
me.update();
run.player.permanentSkills.forEach(skill => {
if (lib.skill[skill] && !me.hasSkill(skill)) me.addSkill(skill);
});
game.addGlobalSkill("mengsan_draw_shuying");
game.addGlobalSkill("mengsan_card_use_shuying");
game.syncState();
_status.event.trigger("gameStart");
await game.mengsanDraw_shuying(me, 4);
game.gameDraw(me, current => current == me ? 0 : 4);
game.phaseLoop(me);
};
const createMode = identityMode => {
const identityElement = identityMode.element || {};
const identityPlayer = identityElement.player || {};
return {
...identityMode,
name: MODE_ID,
connect: false,
start: [
async () => {
lib.translate.restart = "返回";
let run = copy(lib.storage[config.saveKey] || null);
const action = await chooseRun(run);
if (action == "new") {
if (run) {
const confirmNew = await chooseButtons("确认新的开始", [
{ id: "cancel", name: "返回", description: "保留当前征程" },
{ id: "confirm", name: "覆盖存档", description: "当前征程将被清除" },
]);
if (confirmNew == "cancel") {
run = copy(lib.storage[config.saveKey]);
}
else {
const character = await chooseCharacter();
run = createRun(character);
await saveRun(run);
}
}
else {
const character = await chooseCharacter();
run = createRun(character);
await saveRun(run);
}
}
const canRefreshOldMap = !run.map?.completedNodeIds?.length && run.map?.layoutVersion != config.mapLayoutVersion;
if (!run.map || run.map.actIndex != run.actIndex || canRefreshOldMap) {
run.map = generateActMap(run, run.actIndex);
await saveRun(run);
}
const node = await showMap(run);
if (["event", "rest", "shop"].includes(node.type)) {
await finishUtilityNode(run, node);
return;
}
await setupBattle(run, node);
},
],
element: {
...identityElement,
player: {
...identityPlayer,
getHandcardLimit() {
if (this.storage?.mengsanPlayer_shuying) {
const run = _status.mengsanRun_shuying;
const bonus = Number(run?.player?.handLimitBonus || 0);
return Math.max(0, Math.min(8, this.hp) + bonus);
}
let number = Math.max(this.hp, 0);
number = game.checkMod(this, number, "maxHandcardBase", this);
number = game.checkMod(this, number, "maxHandcard", this);
number = game.checkMod(this, number, "maxHandcardFinal", this);
return Math.max(0, number);
},
},
},
game: {
...(identityMode.game || {}),
async mengsanPersistRun_shuying(run) {
await saveRun(run);
},
async mengsanClearRun_shuying() {
await clearRun();
},
async mengsanFailRun_shuying(reason = "征程失败") {
if (_status.mengsanEnding_shuying) return;
_status.mengsanEnding_shuying = true;
await clearRun();
game.log(reason);
game.over(false);
},
async mengsanDraw_shuying(player, number = 1) {
if (!player?.storage?.mengsanPlayer_shuying || number <= 0) return [];
const battle = _status.mengsanBattle_shuying;
const run = _status.mengsanRun_shuying;
if (!battle || battle.drawPile.length + battle.discardPile.length < number) {
await game.mengsanFailRun_shuying("个人牌堆与弃牌堆不足,挑战失败");
return [];
}
const result = [];
while (result.length < number) {
if (!battle.drawPile.length) {
battle.drawPile = battle.discardPile.splice(0);
shuffleBattlePile(run, battle.drawPile);
game.log(player, "洗切了个人弃牌堆");
}
const data = battle.drawPile.shift();
const card = game.createCard(data.name, data.suit, data.number, data.nature);
card.storage.mengsanCard_shuying = copy(data);
card.destroyLog = false;
card.destroyed = (current, position) => {
if (position != "discardPile") return false;
const info = copy(current.storage.mengsanCard_shuying);
if (info.affixes?.includes("annihilate") && current.storage.mengsanUsed_shuying) battle.exhaustPile.push(info);
else battle.discardPile.push(info);
return true;
};
if (data.affixes?.includes("annihilate")) card.addGaintag("湮灭");
if (data.upgrade) card.addGaintag(`强化+${data.upgrade}`);
result.push(card);
}
player.directgain(result);
player.$draw(result.length);
game.log(player, "从个人牌堆摸了", get.cnNumber(result.length), "张牌");
return result;
},
async mengsanFinishBattle_shuying() {
const battle = _status.mengsanBattle_shuying;
if (!battle || battle.resolving || _status.mengsanEnding_shuying) return;
battle.resolving = true;
game.pause();
const run = _status.mengsanRun_shuying;
const node = _status.mengsanNode_shuying;
const encounter = _status.mengsanEncounter_shuying;
run.player.hp = Math.max(1, game.me.hp);
run.player.gold += encounter.gold;
run.statistics.goldEarned += encounter.gold;
run.statistics.defeatedEnemies++;
await chooseBattleReward(run, encounter.boss);
completeNode(run, node.id);
if (encounter.boss) {
if (!enterNextAct(run)) {
const profile = copy(lib.storage[config.profileKey] || { wins: 0, completedRuns: [] });
profile.wins++;
profile.completedRuns.push({
runId: run.runId,
character: run.player.character,
completedNodes: run.statistics.completedNodes,
goldEarned: run.statistics.goldEarned,
completedAt: Date.now(),
});
lib.storage[config.profileKey] = profile;
delete lib.storage[config.saveKey];
await persistModeStorage();
game.over(true);
return;
}
}
await saveRun(run);
game.reload();
},
checkResult() {
if (_status.mengsanEnding_shuying || _status.mengsanBattle_shuying?.resolving) return;
if (!game.me || game.me.isDead()) {
game.mengsanFailRun_shuying("角色死亡,征程结束");
return;
}
const hasEnemy = game.players.some(current => current != game.me && current.side === true && current.isAlive());
if (!hasEnemy) game.mengsanFinishBattle_shuying();
},
},
skill: {
...(identityMode.skill || {}),
mengsan_draw_shuying: {
trigger: { player: "drawBegin" },
firstDo: true,
forced: true,
silent: true,
priority: 10000,
filter(event, player) {
return Boolean(player.storage?.mengsanPlayer_shuying && event.num > 0);
},
async content(event, trigger, player) {
const number = trigger.num;
trigger.cancel();
await game.mengsanDraw_shuying(player, number);
},
},
mengsan_card_use_shuying: {
trigger: { global: "useCard1" },
forced: true,
silent: true,
popup: false,
filter(event) {
return event.player?.storage?.mengsanPlayer_shuying && event.cards?.some(card => card.storage?.mengsanCard_shuying);
},
async content(event, trigger) {
trigger.cards.forEach(card => {
if (card.storage?.mengsanCard_shuying) card.storage.mengsanUsed_shuying = true;
});
},
},
mengsan_yingyong_shuying: {
trigger: { source: "damageBegin1" },
forced: true,
usable: 1,
filter(event, player) {
return Boolean(event.card && _status.currentPhase == player);
},
async content(event, trigger) {
trigger.num++;
},
},
},
translate: {
...(identityMode.translate || {}),
mengsan_draw_shuying: "梦三牌组",
mengsan_card_use_shuying: "梦三词缀",
mengsan_yingyong_shuying: "英勇",
mengsan_yingyong_shuying_info: "锁定技,每回合限一次,你于自己的回合内使用牌造成的伤害+1。",
},
config: {},
help: {
梦三: "梦三 Demo:每次完成节点后保存检查点;玩家使用个人牌组,敌人继续使用 noname 公共牌堆。",
},
};
};
export default async function initMengsan() {
if (lib.config.all.mode.includes(MODE_ID)) return;
await new Promise(resolve => {
const style = lib.init.css(STYLE_PATH, "style", resolve);
style.addEventListener("error", resolve, { once: true });
});
const identityMode = await game.loadModeAsync("identity");
const mode = createMode(identityMode);
mode.splash = "ext:术樱包/pve/images/tianshu.jpg";
game.addMode(MODE_ID, mode, {
translate: "梦三",
config: {},
extension: "术樱包",
});
}
+227
View File
@@ -0,0 +1,227 @@
import config from "./config.js";
const hashText = text => {
let value = 2166136261;
for (let index = 0; index < text.length; index++) {
value ^= text.charCodeAt(index);
value = Math.imul(value, 16777619);
}
return value >>> 0;
};
export const nextRandom = run => {
let value = Number(run.randomState || run.seed || Date.now()) >>> 0;
value += 0x6d2b79f5;
run.randomState = value >>> 0;
let result = value;
result = Math.imul(result ^ result >>> 15, result | 1);
result ^= result + Math.imul(result ^ result >>> 7, result | 61);
return ((result ^ result >>> 14) >>> 0) / 4294967296;
};
export const randomGet = (run, list) => list[Math.floor(nextRandom(run) * list.length)];
const weightedType = (run, weights) => {
const entries = Object.entries(weights);
const total = entries.reduce((sum, entry) => sum + entry[1], 0);
let cursor = nextRandom(run) * total;
for (const [type, weight] of entries) {
cursor -= weight;
if (cursor <= 0) return type;
}
return entries[0][0];
};
const connectFloors = (run, edges, currentFloor, nextFloor) => {
const addEdge = (source, target) => {
if (!edges.some(edge => edge[0] == source.id && edge[1] == target.id)) {
edges.push([source.id, target.id]);
}
};
currentFloor.forEach(source => {
const targets = nextFloor.slice().sort((left, right) => {
const distance = Math.abs(left.routeIndex - source.routeIndex) - Math.abs(right.routeIndex - source.routeIndex);
return distance || left.routeIndex - right.routeIndex;
});
addEdge(source, targets[0]);
if (targets.length > 1 && Math.abs(targets[1].routeIndex - source.routeIndex) <= 1 && nextRandom(run) < 0.45) {
addEdge(source, targets[1]);
}
});
nextFloor.forEach(target => {
if (!edges.some(edge => edge[1] == target.id)) {
const source = currentFloor.slice().sort((left, right) => {
const distance = Math.abs(left.routeIndex - target.routeIndex) - Math.abs(right.routeIndex - target.routeIndex);
return distance || left.routeIndex - right.routeIndex;
})[0];
addEdge(source, target);
}
});
};
const getFloorRoutes = (run, act, floor, lastFloor) => {
const routeCount = Math.max(1, config.routeCount);
if (floor == 0 || floor == lastFloor) return [(routeCount - 1) / 2];
const maximum = Math.max(1, Math.min(routeCount, act.floorNodes[floor] || routeCount));
const configuredMinimum = act.minFloorNodes?.[floor] ?? Math.max(2, maximum - 1);
const minimum = Math.max(1, Math.min(maximum, configuredMinimum));
const count = minimum + Math.floor(nextRandom(run) * (maximum - minimum + 1));
const routes = Array.from({ length: routeCount }, (_, index) => index);
for (let index = routes.length - 1; index > 0; index--) {
const target = Math.floor(nextRandom(run) * (index + 1));
[routes[index], routes[target]] = [routes[target], routes[index]];
}
return routes.slice(0, count).sort((left, right) => left - right);
};
export const generateActMap = (run, actIndex) => {
const act = config.acts[actIndex];
const nodes = [];
const edges = [];
const floors = [];
const lastFloor = act.floorNodes.length - 1;
act.floorNodes.forEach((count, floor) => {
const floorNodes = [];
const routes = getFloorRoutes(run, act, floor, lastFloor);
routes.forEach((routeIndex, index) => {
let type;
if (floor == lastFloor) type = "boss";
else if (floor == 0) type = "battle";
else type = weightedType(run, act.nodeWeights);
const id = `${act.id}_f${floor}_n${index}_${Math.floor(nextRandom(run) * 1e6)}`;
const node = {
id,
floor,
type,
routeIndex,
x: Math.round((routeIndex + 1) * 100 / (config.routeCount + 1)),
y: Math.round(90 - floor * 80 / Math.max(1, lastFloor)),
completed: false,
};
nodes.push(node);
floorNodes.push(node);
});
floors.push(floorNodes);
});
for (let floor = 0; floor < floors.length - 1; floor++) {
connectFloors(run, edges, floors[floor], floors[floor + 1]);
}
return {
layoutVersion: config.mapLayoutVersion,
routeCount: config.routeCount,
actIndex,
actId: act.id,
nodes,
edges,
currentNodeId: null,
completedNodeIds: [],
};
};
const createDeck = () => config.startingDeck.map((entry, index) => ({
id: `mengsan_card_${Date.now()}_${index}`,
suit: entry[0],
number: entry[1],
name: entry[2],
nature: entry[3] || null,
affixes: entry[4] ? entry[4].slice() : [],
upgrade: 0,
}));
export const createRun = character => {
const seed = (Date.now() ^ Math.floor(Math.random() * 0x7fffffff)) >>> 0;
const run = {
schemaVersion: config.schemaVersion,
runId: `mengsan_${Date.now()}`,
seed,
randomState: seed,
status: "running",
revision: 0,
actIndex: 0,
player: {
id: "player_1",
ownerId: "local",
character,
hp: null,
maxHp: null,
gold: 0,
deck: createDeck(),
permanentSkills: [],
temporarySkills: [],
items: [],
handLimitBonus: 0,
},
statistics: {
completedNodes: 0,
defeatedEnemies: 0,
goldEarned: 0,
},
};
run.map = generateActMap(run, 0);
return run;
};
export const getSelectableNodes = run => {
const map = run.map;
if (!map.currentNodeId) {
return map.nodes.filter(node => node.floor == 0 && !node.completed);
}
const ids = map.edges.filter(edge => edge[0] == map.currentNodeId).map(edge => edge[1]);
return map.nodes.filter(node => ids.includes(node.id) && !node.completed);
};
export const completeNode = (run, nodeId) => {
const node = run.map.nodes.find(current => current.id == nodeId);
if (!node || node.completed) return false;
node.completed = true;
run.map.currentNodeId = node.id;
run.map.completedNodeIds.push(node.id);
run.statistics.completedNodes++;
run.revision++;
return true;
};
export const insertStoryNode = (run, sourceId) => {
const map = run.map;
const source = map.nodes.find(node => node.id == sourceId);
const edgeIndex = map.edges.findIndex(edge => edge[0] == sourceId);
if (!source || edgeIndex < 0) return null;
const oldEdge = map.edges[edgeIndex];
const target = map.nodes.find(node => node.id == oldEdge[1]);
if (!target) return null;
const node = {
id: `story_${hashText(`${run.runId}_${sourceId}_${run.revision}`)}`,
floor: source.floor + 0.45,
type: "story",
x: Math.max(8, Math.min(92, Math.round((source.x + target.x) / 2 + (nextRandom(run) - 0.5) * 16))),
y: Math.round((source.y + target.y) / 2),
completed: false,
dynamic: true,
};
map.edges.splice(edgeIndex, 1, [source.id, node.id], [node.id, target.id]);
map.nodes.push(node);
return node;
};
export const enterNextAct = run => {
run.actIndex++;
if (run.actIndex >= config.acts.length) {
run.status = "completed";
return false;
}
run.map = generateActMap(run, run.actIndex);
run.revision++;
return true;
};
export const getNodeEncounter = (run, node) => {
const act = config.acts[run.actIndex];
let pool = act.enemies;
if (node.type == "elite" || node.type == "story") pool = act.eliteEnemies?.length ? act.eliteEnemies : act.enemies;
if (node.type == "boss") return { enemy: act.boss, gold: act.baseGold * 3, boss: true };
return {
enemy: randomGet(run, pool.filter(Boolean)),
gold: node.type == "elite" || node.type == "story" ? act.baseGold * 2 : act.baseGold,
boss: false,
};
};
+258
View File
@@ -0,0 +1,258 @@
.mengsan-overlay-shuying {
position: fixed;
inset: 0;
z-index: 80;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 18px;
background: radial-gradient(circle at 50% 20%, rgba(68, 62, 83, 0.72), rgba(8, 10, 15, 0.96));
color: #f6ead0;
font-family: "Microsoft YaHei", sans-serif;
}
.mengsan-overlay-shuying *,
.mengsan-overlay-shuying *::before,
.mengsan-overlay-shuying *::after {
box-sizing: border-box;
float: none !important;
writing-mode: horizontal-tb !important;
-webkit-writing-mode: horizontal-tb !important;
text-orientation: mixed !important;
letter-spacing: normal;
}
.mengsan-panel-shuying {
position: relative !important;
left: auto !important;
top: auto !important;
right: auto !important;
bottom: auto !important;
display: flex !important;
flex-direction: column !important;
flex: none !important;
width: min(720px, 94vw);
height: auto !important;
min-height: 160px;
max-height: 92vh;
overflow: auto;
box-sizing: border-box;
padding: 22px;
border: 1px solid rgba(232, 202, 140, 0.55);
border-radius: 14px;
background: linear-gradient(145deg, rgba(31, 28, 38, 0.98), rgba(17, 19, 26, 0.98));
box-shadow: 0 20px 70px rgba(0, 0, 0, 0.58), inset 0 1px rgba(255, 255, 255, 0.08);
}
.mengsan-panel-shuying > .mengsan-title-shuying,
.mengsan-panel-shuying > .mengsan-description-shuying,
.mengsan-panel-shuying > .mengsan-choice-list-shuying,
.mengsan-panel-shuying > .mengsan-map-status-shuying,
.mengsan-panel-shuying > .mengsan-map-shuying,
.mengsan-choice-list-shuying > .mengsan-choice-shuying,
.mengsan-choice-shuying > .mengsan-choice-name-shuying,
.mengsan-choice-shuying > .mengsan-choice-description-shuying {
position: relative !important;
left: auto !important;
top: auto !important;
right: auto !important;
bottom: auto !important;
transform: none !important;
}
.mengsan-title-shuying {
display: block !important;
flex: none !important;
width: 100% !important;
height: auto !important;
margin-bottom: 14px;
color: #f2d28e;
font-size: 30px;
font-weight: 700;
text-align: center;
letter-spacing: 4px;
line-height: 1.35;
white-space: normal !important;
}
.mengsan-description-shuying,
.mengsan-map-status-shuying {
display: block !important;
flex: none !important;
width: 100% !important;
height: auto !important;
margin-bottom: 14px;
color: rgba(255, 255, 255, 0.78);
text-align: center;
line-height: 1.6;
}
.mengsan-choice-list-shuying {
display: grid;
flex: none !important;
width: 100% !important;
height: auto !important;
gap: 12px;
}
.mengsan-choice-shuying {
display: block !important;
width: 100% !important;
height: auto !important;
min-height: 64px;
padding: 14px 16px;
border: 1px solid rgba(242, 210, 142, 0.34);
border-radius: 9px;
background: rgba(255, 255, 255, 0.055);
cursor: pointer;
transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}
.mengsan-choice-shuying:hover {
transform: translateY(-2px);
border-color: rgba(242, 210, 142, 0.8);
background: rgba(242, 210, 142, 0.12);
}
.mengsan-choice-shuying.disabled {
cursor: default;
opacity: 0.35;
filter: grayscale(1);
}
.mengsan-choice-name-shuying {
display: block !important;
width: 100% !important;
height: auto !important;
color: #f6dca5;
font-size: 18px;
}
.mengsan-choice-description-shuying {
display: block !important;
width: 100% !important;
height: auto !important;
margin-top: 5px;
color: rgba(255, 255, 255, 0.65);
font-size: 13px;
}
.mengsan-map-panel-shuying {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
min-width: 100%;
min-height: 100%;
max-width: none !important;
max-height: none !important;
padding: clamp(12px, 2vw, 24px);
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
overflow: hidden;
}
.mengsan-map-overlay-shuying {
padding: 0 !important;
background:
radial-gradient(circle at 50% 18%, rgba(67, 59, 82, 0.82), rgba(9, 10, 16, 0.97)),
repeating-linear-gradient(12deg, rgba(255, 255, 255, 0.018) 0 2px, transparent 2px 12px);
}
.mengsan-map-panel-shuying > .mengsan-title-shuying {
margin-bottom: 6px;
font-size: clamp(24px, 3vw, 36px);
}
.mengsan-map-panel-shuying > .mengsan-map-status-shuying {
margin-bottom: 10px;
}
.mengsan-map-shuying {
position: relative;
display: block !important;
flex: 1 1 auto !important;
width: 100%;
height: auto !important;
min-height: 0;
overflow: hidden;
border-radius: 0;
background: transparent;
}
.mengsan-lines-shuying {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
}
.mengsan-lines-shuying line {
vector-effect: non-scaling-stroke;
stroke: rgba(232, 219, 190, 0.68);
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 6 5;
}
.mengsan-lines-shuying line.completed {
stroke: rgba(243, 207, 128, 0.95);
}
.mengsan-lines-shuying line.dynamic {
stroke: rgba(118, 205, 255, 0.95);
stroke-dasharray: 3 4;
}
.mengsan-node-shuying {
position: absolute;
width: 66px;
height: 40px;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border: 2px solid var(--node-color);
border-radius: 50%;
background: radial-gradient(circle at 38% 28%, rgba(255, 255, 255, 0.2), transparent 42%), var(--node-color);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.42);
color: #fff4df;
font-size: 12px;
text-align: center;
user-select: none;
}
.mengsan-node-shuying.selectable {
cursor: pointer;
animation: mengsan-node-pulse-shuying 1.6s infinite;
}
.mengsan-node-shuying.locked {
opacity: 0.45;
filter: grayscale(0.65);
}
.mengsan-node-shuying.completed {
opacity: 0.72;
box-shadow: 0 0 0 3px rgba(244, 211, 139, 0.3);
}
@keyframes mengsan-node-pulse-shuying {
0%, 100% { box-shadow: 0 0 0 0 rgba(242, 207, 132, 0.18), 0 5px 15px rgba(0, 0, 0, 0.42); }
50% { box-shadow: 0 0 0 8px rgba(242, 207, 132, 0), 0 5px 18px rgba(0, 0, 0, 0.55); }
}
@media (max-width: 600px) {
.mengsan-overlay-shuying { padding: 7px; }
.mengsan-panel-shuying { padding: 14px; }
.mengsan-title-shuying { font-size: 24px; }
.mengsan-map-panel-shuying { padding: 8px; }
.mengsan-map-shuying { min-height: 0; }
.mengsan-node-shuying { width: 54px; height: 36px; font-size: 11px; }
}
+12 -224
View File
@@ -1,227 +1,15 @@
const characters = {
zishu_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["zishu_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/zishu_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/zishu.mp3"]
},
// chouniu_boss_shuying: {},
// yinhu_boss_shuying: {},
// maotu_boss_shuying: {},
chenlong_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["chenlong_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/chenlong_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/chenlong.mp3"]
},
// sishe_boss_shuying: {},
wuma_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["wuma_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/wuma_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/wuma_shuying.mp3"]
},
weiyang_boss_shuying: {
sex: "female",
group: "qun",
hp: 4,
skills: ["weiyang_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/weiyang_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/weiyang.mp3"]
},
shenhou_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["shenhou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/shenhou_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/shenhou.mp3"]
},
// youji_boss_shuying: {},
xvgou_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["xvgou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/xvgou_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/xvgou.mp3"]
},
// haizhu_boss_shuying: {},
import shiershengxiao from "./sets/shiershengxiao/character.js";
import zhuoguiquxie from "./sets/zhuoguiquxie/character.js";
import qingqingzijin from "./sets/qingqingzijin/character.js";
import tianshu from "./sets/tianshu/character.js";
import mergeBossModules from "./merge.js";
heibaiwuchang_boss_shuying:
{
sex: "male",
group: "qun",
hp: 4,
skills: ["mizui_shuying", "qiangzheng_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png"
},
niutoumamian_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["shiyv_shuying", "manji_shuying", "xiaoshou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png"
},
yvsai_boss_shuying: {
sex: "female",
group: "qun",
hp: 4,
skills: ["anchao_shuying", "guixi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/yvsai_boss_shuying.png"
},
huangfeng_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["mingchong_shuying", "duzhen_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png"
},
riyeyoushen_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["huiyun_shuying", "yezhong_shuying", "zhoucha_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png"
},
caocao_boss_shuying: {
sex: "male",
group: "wei",
hp: 4,
skills: ["jianxiong_shuying", "lingba_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/caocao_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3"]
},
simayi_boss_shuying: {
sex: "male",
group: "wei",
hp: 4,
skills: ["fankui_shuying", "guicai_shuying", "langgu_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/simayi_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3"]
},
lvbu_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["mashu", "wushuang_shuying", "shenij_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/lvbu_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3"]
},
dongzhuo_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["jiuchi_shuying", "roulin_shuying", "baonue_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/dongzhuo_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3"]
},
shuishengonggong_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["tuanliu_shuying", "juehong_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/shuishengonggong_boss_shuying.png"
},
// huoshenzhurong_boss_shuying: {},
baiqi_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["changsheng_shuying", "shashen_shuying", "wuan_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/baiqi_boss_shuying.png"
},
kuafu_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["shenqu_shuying", "lieben_shuying", "yinjiang_shuying", "zhuri_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/kuafu_boss_shuying.png"
},
shaohao_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["baiyi_shuying", "shenen_shuying",],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/shaohao_boss_shuying.png"
},
xuannv_boss_shuying: {
sex: "female",
group: "shen",
hp: 4,
skills: ["xuanlie_shuying", "jiutian_shuying", "dishi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/xuannv_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3"]
},
hanba_boss_shuying: {
sex: "female",
group: "shen",
hp: 4,
skills: ["xinji_shuying", "zhiri_shuying", "fenshi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/hanba_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/hanba_boss_shuying.mp3"]
},
};
// 汇总所有主题武将,PVE 玩法只需引用唯一的 BOSS ID。
const characters = mergeBossModules("武将", [
["十二生肖", shiershengxiao],
["捉鬼驱邪", zhuoguiquxie],
["青青子衿", qingqingzijin],
["天书乱斗", tianshu],
]);
export default characters;
+10 -202
View File
@@ -1,205 +1,13 @@
import { lib, game, ui, get, ai, _status } from "../../../../noname.js";
import tianshuConfig from "../../tianshu/config.js";
import zhuoguiquxie from "./sets/zhuoguiquxie/dynamicTranslate.js";
import qingqingzijin from "./sets/qingqingzijin/dynamicTranslate.js";
import tianshu from "./sets/tianshu/dynamicTranslate.js";
import mergeBossModules from "./merge.js";
const getTianshuDifficulty = () => {
return _status[tianshuConfig.settings.difficultyStatusKey] || "normal";
};
const dynamicTranslates = {
//黑白无常
mizui_shuying(player, skill) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2
return `锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的${num}张牌。`
},
qiangzheng_shuying(player, skill) {
let num = 2;
if (getTianshuDifficulty() == "hard") num = 4;
if (getTianshuDifficulty() == "nightmare") num = 6;
return `锁定技,敌方角色的结束阶段,若其手牌数小于${num},你获得其所有手牌。`
},
xixing_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "锁定技,准备阶段,你对每名敌方角色各造成2点雷电伤害,然后你回复等量体力";
}
return "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力。";
},
//鱼鳃
guixi_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸${num}张牌。`;
},
//黄蜂
mingchong_shuying(player, skill) {
if (getTianshuDifficulty() == "nightmare") {
return "锁定技,你死亡时,杀死你的角色弃置所有牌。"
}
return "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。"
},
duzhen_shuyingg(player, skill) {
if (getTianshuDifficulty() == "nightmare") {
return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。"
}
return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置2张牌(优先装备区的牌)。"
},
//日夜游神
mingchong_shuying(player, skill) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2
return `出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成${num}点伤害。`
},
yezhong_shuying(player, skill) {
let num = 1;
if (getTianshuDifficulty() == "hard") num = 2;
if (getTianshuDifficulty() == "nightmare") num = 3;
return `锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃${num}张手牌。`
},
zhoucha_shuying(player, skill) {
let num = 1;
if (getTianshuDifficulty() == "hard") num = 2;
if (getTianshuDifficulty() == "nightmare") num = 3;
return `锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+${num}。`
},
//曹操
lingba_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成${num}点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。`
},
yishen_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "当你回复体力时,可以改为获得所有敌人各一张随机装备。";
}
return "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。";
},
//司马懿
yuanlv_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成过1点伤害。";
}
return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。";
},
//吕布
wushuang_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限${num}次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。`;
},
shenij_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
let card = 2;
let sha = 1;
let target = 1;
if (difficulty == "hard") {
card = 3;
sha = 2;
} else if (difficulty == "nightmare") {
card = 4;
sha = 2;
target = 2;
}
return `判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸${card}张牌;出牌阶段,你可以多使用${sha}张【杀】,你的【杀】可以多指定${target}名角色为目标。`;
},
zhanjia_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 3 : 2
return `锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸${num}张牌。`
},
//董卓
baonue_shuying(player, skill) {
let num = 3;
if (getTianshuDifficulty() == "hard") num = 4;
if (getTianshuDifficulty() == "nightmare") num = 5;
return `锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为${num}。`
},
//水神共工
tuanliu_shuying(player, skill) {
let hp = 2;
let card = 4;
let damage = 1;
if (getTianshuDifficulty() == "nightmare") {
hp = 3;
card = 6;
}
return `锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复${hp}点体力;大于5,你摸${card}张牌;大于9,你对所有敌方角色造成${damage}点伤害。`
},
//少昊
baiyi_shuying(player, skill) {
let num = 1;
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
return `锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其${num}张牌;若当前轮数小于5,对其造成${num}点雷电伤害;若当前轮数小于7,其随机弃置${num}张牌。`;
},
//玄女
xuanlie_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "normal" ? 1 : 2;
return `锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成${num}点伤害。`;
},
jiutian_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "normal" ? 1 : 2;
const extra =
difficulty == "normal" || difficulty == "hard"
? ""
: "若这些牌包含4种花色,这些角色再额外失去1点体力。";
return `锁定技,准备阶段,你获得所有敌方角色各${num}张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。${extra}`;
},
xinji_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "nightmare" ? 2 : 1;
return `锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成${num}点伤害。`
},
zhiri_shuying(player, skill) {
let num = 1;
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
return `锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸${num}张牌。`
},
};
// 汇总存在动态描述的主题技能;无动态描述的主题不需要空模块占位。
const dynamicTranslates = mergeBossModules("动态翻译", [
["捉鬼驱邪", zhuoguiquxie],
["青青子衿", qingqingzijin],
["天书乱斗", tianshu],
]);
export default dynamicTranslates;
+16
View File
@@ -0,0 +1,16 @@
// 合并各主题的同类数据;重复 ID 保留先加载项,跳过后加载项,避免中断整个 BOSS 包。
const mergeBossModules = (dataType, modules) => {
const result = {};
for (const [moduleName, data] of modules) {
for (const [id, value] of Object.entries(data || {})) {
if (Object.prototype.hasOwnProperty.call(result, id)) {
console.warn(`术樱包活动BOSS:跳过${moduleName}中重复的${dataType}ID“${id}”`);
continue;
}
result[id] = value;
}
}
return result;
};
export default mergeBossModules;
+44
View File
@@ -0,0 +1,44 @@
const characters = {
caocao_boss_shuying: {
sex: "male",
group: "wei",
hp: 4,
skills: ["jianxiong_shuying", "lingba_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/caocao_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3"]
},
simayi_boss_shuying: {
sex: "male",
group: "wei",
hp: 4,
skills: ["fankui_shuying", "guicai_shuying", "langgu_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/simayi_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3"]
},
lvbu_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["mashu", "wushuang_shuying", "shenij_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/lvbu_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3"]
},
dongzhuo_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["jiuchi_shuying", "roulin_shuying", "baonue_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/青青子衿/dongzhuo_boss_shuying.jpg",
dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3"]
},
};
export default characters;
@@ -0,0 +1,71 @@
import { getTianshuDifficulty } from "../../shared.js";
const dynamicTranslates = {
//曹操
lingba_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成${num}点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。`
},
yishen_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "当你回复体力时,可以改为获得所有敌人各一张随机装备。";
}
return "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。";
},
//司马懿
yuanlv_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成过1点伤害。";
}
return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。";
},
//吕布
wushuang_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限${num}次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。`;
},
shenij_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
let card = 2;
let sha = 1;
let target = 1;
if (difficulty == "hard") {
card = 3;
sha = 2;
} else if (difficulty == "nightmare") {
card = 4;
sha = 2;
target = 2;
}
return `判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸${card}张牌;出牌阶段,你可以多使用${sha}张【杀】,你的【杀】可以多指定${target}名角色为目标。`;
},
zhanjia_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 3 : 2
return `锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸${num}张牌。`
},
//董卓
baonue_shuying(player, skill) {
let num = 3;
if (getTianshuDifficulty() == "hard") num = 4;
if (getTianshuDifficulty() == "nightmare") num = 5;
return `锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为${num}。`
},
};
export default dynamicTranslates;
+676
View File
@@ -0,0 +1,676 @@
import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js";
const skills = {
//曹操 over
jianxiong_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "damageEnd" },
filter(event, player) {
return event.num > 0;
},
async cost(event, trigger, player) {
event.result = await player.chooseBool(get.prompt2(event.skill))
.set("ai", () => true)
.forResult();
},
async content(event, trigger, player) {
if (get.itemtype(trigger.cards) == "cards" && get.position(trigger.cards[0], true) == "o") {
await player.gain(trigger.cards, "gain2");
}
await player.draw();
},
ai: {
maixie: true,
maixie_hp: true,
effect: {
target(card, player, target) {
if (player.hasSkillTag("jueqing", false, target)) return [1, -1];
if (get.tag(card, "damage")) return [1, 0.6];
},
},
},
},
lingba_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "phaseBegin" },
forced: true,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn()) && game.players.every(current => player.countCards("h") >= current.countCards("h"));
},
async content(event, trigger, player) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
const enemies = player.getEnemies(null, false).filter(target => target.isIn());
if (player.countCards("h") >= player.hp * 2) {
for (const target of enemies) {
player.line(target);
await target.damage(num, player);
}
}
else {
const target = enemies.randomGet();
player.line(target);
await target.damage(num, player);
}
},
},
yishen_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "recoverBegin" },
filter(event, player) {
return player.getEnemies(null, false).some(target => target.countGainableCards(player, "e") > 0);
},
async cost(event, trigger, player) {
event.result = await player.chooseBool(get.prompt2(event.skill))
.set("ai", () => {
const player = get.player();
const trigger = get.event().getTrigger();
if (player.hp <= 1) return false;
if (player.hp + trigger.num <= 2) return false;
const enemies = player.getEnemies(null, false).filter(target => {
return target.isIn() && target.countGainableCards(player, "e") > 0;
});
if (!enemies.length) return false;
const recoverEffect = get.recoverEffect(player, player, player);
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
let total = 0;
let max = 0;
for (const target of enemies) {
for (const card of target.getCards("e")) {
const value = get.value(card, target);
total += value;
if (value > max) max = value;
}
}
if (max < 5) return false;
return total >= recoverEffect + 4;
}
let best = 0;
for (const target of enemies) {
for (const card of target.getCards("e")) {
const value = get.value(card, target);
if (value > best) best = value;
}
}
if (best < 5) return false;
return best >= recoverEffect;
})
.forResult();
},
async content(event, trigger, player) {
const enemies = player.getEnemies(null, false).filter(target => target.countGainableCards(player, "e") > 0);
const difficulty = getTianshuDifficulty();
trigger.cancel();
if (difficulty == "nightmare") {
for (const target of enemies) {
player.line(target);
await player.gainPlayerCard({ target, position: "e", forced: true });
}
}
else {
const target = enemies.randomGet();
player.line(target);
await player.gainPlayerCard({ target, position: "e", forced: true });
}
},
ai: {
threaten: 1.4,
effect: {
target(card, player, target) {
if (get.tag(card, "recover") && target.hasSkill("yishen_shuying")) {
if (target.hp <= 1) return [1, 1.2];
if (target.hp <= 2) return [1, 0.8];
const enemies = target.getEnemies(null, false).filter(current => {
return current.isIn() && current.countGainableCards(target, "e") > 0;
});
if (!enemies.length) return;
let best = 0;
for (const enemy of enemies) {
for (const equip of enemy.getCards("e")) {
best = Math.max(best, get.value(equip, enemy));
}
}
if (best >= 5) return [1, 0.4];
}
},
},
},
},
//司马懿 over
fankui_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "damageEnd" },
logTarget: "source",
filter(event, player) {
return (
event.num > 0 &&
event.source?.isIn() &&
player.getEnemies(null, false).includes(event.source) &&
event.source.hasGainableCards(player, "he")
);
},
async content(event, trigger, player) {
await player.gainPlayerCard({
target: trigger.source,
position: "he",
forced: true,
});
},
ai: {
maixie_defend: true,
effect: {
target(card, player, target) {
if (player.countCards("he") > 1 && get.tag(card, "damage")) {
if (player.hasSkillTag("jueqing", false, target)) return [1, -1.5];
if (target.getEnemies(null, false).includes(player)) return [1, 1];
}
},
},
},
},
guicai_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { global: "judge" },
filter(event, player) {
return player.countCards("hes") > 0;
},
async cost(event, trigger, player) {
event.result = await player.chooseCard(`${get.translation(trigger.player)}的${trigger.judgestr || ""}判定为${get.translation(trigger.player.judging[0])},${get.prompt(event.skill)}`, "hes", card => {
const player = get.player();
const mod2 = game.checkMod(card, player, "unchanged", "cardEnabled2", player);
if (mod2 != "unchanged") return mod2;
const mod = game.checkMod(card, player, "unchanged", "cardRespondable", player);
if (mod != "unchanged") return mod;
return true;
}).set("ai", card => {
const trigger = get.event().getTrigger(), player = get.player(), judging = trigger.player.judging[0];
const result = trigger.judge(card) - trigger.judge(judging);
const attitude = player.getEnemies(null, false).includes(trigger.player) ? -1 : 1;
let value = get.value(card, player);
value /= get.subtype(card) == "equip2" ? 2 : 4;
if (result == 0) return 0;
return attitude > 0 ? result - value : -result - value;
}).set("judging", trigger.player.judging[0]).forResult();
},
popup: false,
async content(event, trigger, player) {
const next = player.respond(event.cards, event.name, "highlight", "noOrdering");
await next;
const { cards } = next;
if (cards?.length) {
if (trigger.player.judging[0].clone) {
trigger.player.judging[0].clone.classList.remove("thrownhighlight");
game.broadcast(card => {
if (card.clone) card.clone.classList.remove("thrownhighlight");
}, trigger.player.judging[0]);
game.addVideo("deletenode", player, get.cardsInfo([trigger.player.judging[0].clone]));
}
await game.cardsDiscard(trigger.player.judging[0]);
trigger.player.judging[0] = cards[0];
trigger.orderingCards.addArray(cards);
game.log(trigger.player, "的判定牌改为", cards);
await game.delay(2);
}
},
ai: { rejudge: true, tag: { rejudge: 1 } },
},
langgu_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "gainAfter" },
forced: true,
filter(event, player) {
if (player.hasSkill("langgu_shuying_used")) return false;
return game.hasPlayer(current => {
return (
current != player &&
player.getEnemies(null, false).includes(current) &&
event.getl?.(current)?.cards2?.length
);
});
},
async content(event, trigger, player) {
const source = game.findPlayer(current => {
return (
current != player &&
player.getEnemies(null, false).includes(current) &&
trigger.getl?.(current)?.cards2?.length
);
});
if (!source) return;
player.addTempSkill("langgu_shuying_used", "roundStart");
const result = await player.judge(card => get.suit(card) == "spade" ? 2 : 0).forResult();
if (result.bool && source.isIn() && source.countDiscardableCards(player, "h") > 0) {
player.removeSkill("langgu_shuying_used");
player.line(source);
const card = source.getCards("h").randomGet();
await source.discard(card);
}
},
subSkill: {
used: { charlotte: true },
},
},
yuanlv_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { source: "damageBegin4" },
filter(event, player) {
return event.card && get.type2(event.card) == "trick" && event.player?.isIn() && player.getEnemies(null, false).includes(event.player);
},
async cost(event, trigger, player) {
event.result = await player.chooseBool(get.prompt(event.skill, trigger.player))
.set("ai", () => true)
.forResult();
},
async content(event, trigger, player) {
const target = trigger.player;
trigger.cancel();
await player.draw();
target.line(player);
if (getTianshuDifficulty() == "nightmare") {
await player.damage(1, target, "unreal");
}
else {
await player.damage(1, target);
}
},
},
//吕布 over
wushuang_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:2",
trigger: { source: "damageBegin1" },
forced: true,
locked: true,
usable(skill, player) {
return getTianshuDifficulty() == "nightmare" ? 2 : 1;
},
logTarget: "player",
filter(event, player) {
const target = event.player;
const evtx = event.getParent(2);
const card = event.card;
if (!card || !["sha", "juedou"].includes(card.name)) return false;
if (card.name == "sha") {
return !target.hasHistory("useCard", evt => {
return evt.card.name == "shan" && evt.respondTo && evt.getParent(3) == evtx;
});
}
return !target.hasHistory("respond", evt => {
return evt.card.name == "sha" && evt.respondTo && evt.getParent(3) == evtx;
});
},
async content(event, trigger, player) {
trigger.num++;
},
group: ["wushuang_shuying_1", "wushuang_shuying_2"],
subSkill: {
1: {
audio: "wushuang_shuying",
sourceSkill: "wushuang_shuying",
trigger: { player: "useCardToPlayered" },
forced: true,
logTarget: "target",
filter(event, player) {
return event.card.name == "sha" && !event.getParent().directHit.includes(event.target);
},
async content(event, trigger, player) {
const id = trigger.target.playerid;
const map = trigger.getParent().customArgs;
if (!map[id]) map[id] = {};
if (typeof map[id].shanRequired == "number") map[id].shanRequired++;
else map[id].shanRequired = 2;
},
ai: {
directHit_ai: true,
skillTagFilter(player, tag, arg) {
if (arg.card.name != "sha" || arg.target.countCards("h", "shan") > 1) return false;
},
},
},
2: {
audio: "wushuang_shuying",
sourceSkill: "wushuang_shuying",
trigger: { player: "useCardToPlayered", target: "useCardToTargeted" },
forced: true,
logTarget(trigger, player) {
return player == trigger.player ? trigger.target : trigger.player;
},
filter(event, player) {
return event.card.name == "juedou";
},
async content(event, trigger, player) {
const id = (player == trigger.player ? trigger.target : trigger.player).playerid;
const idt = trigger.target.playerid;
const map = trigger.getParent().customArgs;
if (!map[idt]) map[idt] = {};
if (!map[idt].shaReq) map[idt].shaReq = {};
if (!map[idt].shaReq[id]) map[idt].shaReq[id] = 1;
map[idt].shaReq[id]++;
},
ai: {
directHit_ai: true,
skillTagFilter(player, tag, arg) {
if (
arg.card.name != "juedou" ||
Math.floor(arg.target.countCards("h", "sha") / 2) > player.countCards("h", "sha")
) {
return false;
}
},
},
},
},
ai: {
threaten: 1.8,
directHit_ai: true,
},
},
shenij_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:2",
group: [
"shenij_shuying_judge",
"shenij_shuying_draw",
"shenij_shuying_sha",
"shenij_shuying_target",
],
subSkill: {
judge: {
audio: "shenij_shuying",
trigger: { player: "phaseJudgeBegin" },
filter(event, player) {
return player.countCards("h") >= 2 && player.countCards("j") > 0;
},
async cost(event, trigger, player) {
event.result = await player
.chooseToDiscard("h", 2, get.prompt2(event.skill))
.set("ai", card => {
const player = get.player();
if (!player.countCards("j")) return 0;
return 7 - get.value(card);
})
.forResult();
},
async content(event, trigger, player) {
const cards = player.getCards("j");
await player.discard(cards);
},
ai: {
effect: {
target(card, player, target) {
if (get.type(card) == "delay") return [0, 2];
},
},
},
},
draw: {
audio: "shenij_shuying",
trigger: { player: "phaseDrawBegin2" },
forced: true,
filter(event, player) {
return !event.numFixed;
},
async content(event, trigger, player) {
let num = 2;
if (getTianshuDifficulty() == "hard") num = 3;
if (getTianshuDifficulty() == "nightmare") num = 4;
trigger.num += num;
},
},
sha: {
audio: "shenij_shuying",
forced: true,
mod: {
cardUsable(card, player, num) {
let shaNum = 2;
if (getTianshuDifficulty() == "normal") shaNum = 1;
if (card.name == "sha") return num + shaNum;
},
},
},
target: {
audio: "shenij_shuying",
trigger: { player: "useCard2" },
filter(event, player) {
if (event.card.name != "sha") return false;
const info = get.info(event.card);
if (info.allowMultiple == false || info.multitarget || !event.targets) return false;
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1;
return game.countPlayer(current => {
return !event.targets.includes(current) &&
lib.filter.targetEnabled2(event.card, player, current) &&
lib.filter.targetInRange(event.card, player, current);
}) > 0;
},
async cost(event, trigger, player) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1;
event.result = await player
.chooseTarget(
get.prompt(event.skill),
`为此【杀】额外指定至多${get.cnNumber(num)}名目标`,
[1, num],
(card, player, target) => {
const trigger = get.event().getTrigger();
return !trigger.targets.includes(target) &&
lib.filter.targetEnabled2(trigger.card, player, target) &&
lib.filter.targetInRange(trigger.card, player, target);
}
)
.set("ai", target => {
const player = get.player();
if (!player.getEnemies(null, false).includes(target)) return 0;
return get.effect(target, get.event().getTrigger().card, player, player);
})
.forResult();
},
async content(event, trigger, player) {
for (const target of event.targets) {
player.line(target);
trigger.targets.add(target);
game.log(target, "成为了", trigger.card, "的额外目标");
}
},
ai: {
effect: {
player_use(card, player, target) {
if (card.name == "sha" && player.getEnemies(null, false).includes(target)) return [1, 0.5];
},
},
},
},
},
},
zhanjia_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:2",
trigger: { player: "damageBegin4" },
forced: true,
locked: true,
usable: 1,
filter(event, player) {
return event.num > 2;
},
async content(event, trigger, player) {
trigger.num = 2;
await player.draw(getTianshuDifficulty() == "nightmare" ? 3 : 2);
},
ai: {
filterDamage: true,
effect: {
target(card, player, target) {
if (get.tag(card, "damage")) {
if (player.hasSkillTag("jueqing", false, target)) return;
return [1, 0.8];
}
},
},
},
},
//董卓 over
jiuchi_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
enable: "chooseToUse",
filterCard(card, player) {
return get.suit(card, player) == "spade";
},
position: "h",
viewAs: { name: "jiu" },
viewAsFilter(player) {
return player.countCards("h", card => get.suit(card, player) == "spade") > 0;
},
prompt: "将一张黑桃手牌当【酒】使用",
check(card) {
return 6 - get.value(card);
},
ai: {
order: 3.5,
respondJiu: true,
skillTagFilter(player) {
return player.countCards("h", card => get.suit(card, player) == "spade") > 0;
},
},
},
roulin_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "useCardToPlayered", target: "useCardToTargeted" },
forced: true,
locked: true,
logTarget(trigger, player) {
return player == trigger.player ? trigger.target : trigger.player;
},
filter(event, player) {
if (event.card.name != "sha") return false;
if (player == event.player) return event.target.hasSex("female");
return event.player.hasSex("female");
},
async content(event, trigger, player) {
const id = (player == trigger.player ? trigger.target : player).playerid;
const map = trigger.getParent().customArgs;
if (!map[id]) map[id] = {};
if (typeof map[id].shanRequired == "number") map[id].shanRequired++;
else map[id].shanRequired = 2;
},
ai: {
halfneg: true,
directHit_ai: true,
skillTagFilter(player, tag, arg) {
if (tag === "directHit_ai") return;
if (arg.card.name != "sha" || !arg.target.hasSex("female") || arg.target.countCards("h", "shan") > 1) {
return false;
}
},
},
},
baonue_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { player: "phaseBegin" },
forced: true,
locked: true,
filter(event, player) {
return player.getDamagedHp() > 0;
},
async content(event, trigger, player) {
let maxNum = 3;
if (getTianshuDifficulty() == "hard") maxNum = 4;
if (getTianshuDifficulty() == "nightmare") maxNum = 5;
const num = Math.min(maxNum, player.getDamagedHp());
await player.draw(num);
const result = await player
.chooseTarget(`对至多${get.cnNumber(num)}名角色造成1点伤害`, [1, num], (card, player, target) => {
return target.isIn() && player.getEnemies(null, false).includes(target);
})
.set("ai", target => {
const player = get.player();
return get.damageEffect(target, player, player);
})
.forResult();
if (result.bool) {
for (const target of result.targets) {
player.line(target);
await target.damage(1, player);
}
}
await player.loseHp();
},
ai: {
threaten: 2,
},
},
qubu_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/青青子衿:1",
trigger: { global: "useCardToPlayered" },
forced: true,
locked: true,
filter(event, player) {
return (
event.card.name == "sha" &&
event.player?.isIn() &&
!player.getEnemies(null, false).includes(event.player) &&
event.targets?.length > 0
);
},
async content(event, trigger, player) {
const result = await player
.judge(card => get.color(card) == "black" ? 2 : -1)
.set("judge2", result => result.bool)
.forResult();
if (result.bool) {
const targets = trigger.targets.filter(target => target.isIn());
for (const target of targets) {
player.line(target);
await target.damage(1, player);
}
}
},
ai: {
threaten: 1.8,
},
},
};
export default skills;
+41
View File
@@ -0,0 +1,41 @@
const translates = {
caocao_boss_shuying: "曹操",
jianxiong_shuying: "奸雄",
jianxiong_shuying_info: "当你受到伤害后,你可以获得对你造成伤害的牌并摸一张牌。",
lingba_shuying: "凌霸",
lingba_shuying_info: "锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成1点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。",
yishen_shuying: "疑神",
yishen_shuying_info: "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。",
simayi_boss_shuying: "司马懿",
fankui_shuying: "反馈",
fankui_shuying_info: "当你受到1点伤害后,你可以获得伤害来源的一张牌。",
guicai_shuying: "鬼才",
guicai_shuying_info: "当一名角色的判定牌生效前,你可以打出一张牌代替之。",
langgu_shuying: "狼顾",
langgu_shuying_info: " 锁定技,每回合限一次,当你获得其他角色的牌时,进行一次判定:若结果为黑桃,随机弃置其1张手牌,且视为此技能本回合未发动过。",
yuanlv_shuying: "远虑",
yuanlv_shuying_info: "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。",
lvbu_boss_shuying: "吕布",
wushuang_shuying: "无双",
wushuang_shuying_info: "锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限1次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。",
shenij_shuying: "神戟",
shenij_shuying_info: "判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸两张牌;出牌阶段,你可以多使用一张【杀】,你的【杀】可以多指定一名角色为目标。",
zhanjia_shuying: "战甲",
zhanjia_shuying_info: "锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸两张牌。",
dongzhuo_boss_shuying: "董卓",
jiuchi_shuying: "酒池",
jiuchi_shuying_info: "你可以将一张黑桃手牌当【酒】使用。",
roulin_shuying: "肉林",
roulin_shuying_info: "锁定技,你对女性角色使用的【杀】和女性角色对你使用的【杀】均需使用两张【闪】才能抵消。",
baonue_shuying: "暴虐",
baonue_shuying_info: "锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为3。",
qubu_shuying: "驭布",
qubu_shuying_info: "锁定技,当友方角色使用【杀】指定目标时,进行一次判定:若结果为黑桃,你对此【杀】的所有目标造成1点伤害。",
};
export default translates;
+70
View File
@@ -0,0 +1,70 @@
const characters = {
zishu_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["zishu_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/zishu_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/zishu.mp3"]
},
// chouniu_boss_shuying: {},
// yinhu_boss_shuying: {},
// maotu_boss_shuying: {},
chenlong_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["chenlong_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/chenlong_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/chenlong.mp3"]
},
// sishe_boss_shuying: {},
wuma_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["wuma_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/wuma_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/wuma_shuying.mp3"]
},
weiyang_boss_shuying: {
sex: "female",
group: "qun",
hp: 4,
skills: ["weiyang_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/weiyang_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/weiyang.mp3"]
},
shenhou_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["shenhou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/shenhou_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/shenhou.mp3"]
},
// youji_boss_shuying: {},
xvgou_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["xvgou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/十二生肖/xvgou_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/xvgou.mp3"]
},
// haizhu_boss_shuying: {},
};
export default characters;
+297
View File
@@ -0,0 +1,297 @@
import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js";
const skills = {
//子鼠
zishu_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
enable: "phaseUse",
usable: 1,
filter(event, player) {
return game.hasPlayer(current => {
return current != player && current.countCards("h") > player.countCards("h");
});
},
async content(event, trigger, player) {
while (player.isIn()) {
const max = Math.max(...game.players.map(current => current.countCards("h")));
if (player.countCards("h") >= max) break;
const targets = game.filterPlayer(current => {
return current != player && current.countCards("h") > player.countCards("h");
});
if (!targets.length) break;
const hasEnemy = targets.some(target => player.getEnemies(null, false).includes(target));
const result = await player
.chooseTarget("恣疏:获得一名手牌数大于你的角色一张手牌", (card, player, target) => {
return target != player && target.countCards("h") > player.countCards("h");
})
.set("ai", target => {
const player = get.player();
const enemies = player.getEnemies(null, false);
const isEnemy = enemies.includes(target);
const hasEnemy = game.hasPlayer(current => {
return current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current);
});
if (hasEnemy && !isEnemy) return 0;
if (isEnemy) return 8 + target.countCards("h") - get.attitude(player, target);
return 1 + target.countCards("h") / 10;
})
.forResult();
if (!result.bool) break;
const target = result.targets[0];
player.line(target);
await player.gainPlayerCard({
target,
position: "h",
forced: true,
});
}
},
ai: {
order: 4.5,
result: {
player(player) {
const enemies = player.getEnemies(null, false);
if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current))) {
return 1;
}
if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && !enemies.includes(current))) {
return 0.2;
}
return 0;
},
},
threaten: 1.4,
},
},
//辰龙
chenlong_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
enable: "phaseUse",
limited: true,
skillAnimation: true,
animationColor: "fire",
selectTarget: 1,
filter(event, player) {
return !player.storage.chenlong_shuying_used && game.hasPlayer(current => current != player && player.getEnemies(null, false).includes(current));
},
filterTarget(card, player, target) {
return target != player && player.getEnemies(null, false).includes(target);
},
async content(event, trigger, player) {
const target = event.targets[0];
const result = await player.chooseControl([1, 2, 3, 4, 5])
.set("prompt", "失去任意点体力,然后对一名其他角色造成等量伤害")
.set("ai", () => 4)
.forResult();
const num = result.control;
if (!num) return;
player.storage.chenlong_shuying_used = true;
player.storage.chenlong_shuying_dying = true;
await player.loseHp(num);
delete player.storage.chenlong_shuying_dying;
player.awakenSkill(event.name);
if (target.isIn()) {
player.line(target);
await target.damage(num, player);
}
},
ai: {
order: 4,
result: {
target(player, target) {
if (!player.getEnemies(null, false).includes(target)) return 0;
const effect = get.damageEffect(target, player, target);
if (effect >= 0) return 0;
return target.hp <= 2 ? effect * 2 : effect;
},
},
},
group: "chenlong_shuying_dying",
subSkill: {
dying: {
trigger: { player: ["dying", "dyingAfter"] },
forced: true,
popup: false,
charlotte: true,
filter(event, player) {
return player.storage.chenlong_shuying_dying && player.hp <= 0;
},
async content(event, trigger, player) {
await player.recoverTo(1);
if (player.maxHp > 0) await player.loseMaxHp();
},
},
},
},
//午马
wuma_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
trigger: { target: "useCardToTargeted" },
forced: true,
locked: true,
filter(event, player) {
return event.player != player && get.type2(event.card) == "trick";
},
async content(event, trigger, player) {
await player.draw();
},
group: ["wuma_shuying_turn", "wuma_shuying_skip"],
subSkill: {
turn: {
audio: "wuma_shuying",
trigger: { player: "turnOverBefore" },
forced: true,
filter(event, player) {
return !player.isTurnedOver();
},
async content(event, trigger, player) {
trigger.cancel();
},
},
skip: {
audio: "wuma_shuying",
trigger: { player: ["phaseAnySkipped", "phaseAnyCancelled"] },
forced: true,
async content(event, trigger, player) {
game.log(player, "恢复了", trigger.name);
await player[trigger.name]();
},
},
},
ai: {
effect: {
target(card, player, target) {
if (player != target && get.type2(card) == "trick") return [1, 0.5];
},
},
},
},
//未羊
weiyang_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
enable: "phaseUse",
usable: 1,
position: "he",
selectCard: [1, Infinity],
filter(event, player) {
return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current));
},
filterCard(card, player) {
const type = get.type(card);
const friends = game.countPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current));
return ui.selected.cards.length < friends && !ui.selected.cards.some(cardx => get.type(cardx) == type);
},
check(card) {
return 6 - get.value(card);
},
async content(event, trigger, player) {
const num = event.cards.length;
const friends = game.filterPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current));
if (!friends.length) return;
const result = await player.chooseTarget(`令至多${get.cnNumber(num)}名角色回复1点体力`, [1, Math.min(num, friends.length)], (card, player, target) => {
return target.isDamaged() && !player.getEnemies(null, false).includes(target);
}).set("ai", target => get.recoverEffect(target, get.player(), get.player())).forResult();
if (result.bool) {
for (const target of result.targets) {
player.line(target);
await target.recover();
}
}
},
ai: {
order: 6,
result: {
player(player) {
return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current) && get.recoverEffect(current, player, player) > 0) ? 1 : 0;
},
},
},
},
//申猴
shenhou_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
trigger: { target: "useCardToTargeted" },
filter(event, player) {
return event.card.name == "sha";
},
async cost(event, trigger, player) {
event.result = await player
.chooseBool(get.prompt2(event.skill))
.set("ai", () => {
const player = get.player();
const trigger = get.event().getTrigger();
return get.effect(player, trigger.card, trigger.player, player) < 0;
})
.forResult();
},
async content(event, trigger, player) {
const result = await player
.judge(card => get.color(card) == "red" ? 2 : -1)
.set("judge2", result => result.bool)
.forResult();
if (result.bool) {
trigger.getParent().excluded.add(player);
}
},
ai: {
effect: {
target(card, player, target) {
if (card.name == "sha") return [1, 0.6];
},
},
},
},
//戌狗
xvgou_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/十二生肖:1",
forced: true,
group: ["xvgou_shuying_cancel", "xvgou_shuying_damage"],
mod: {
targetInRange(card, player, target) {
if (card.name == "sha" && get.color(card) == "red") return true;
},
},
subSkill: {
cancel: {
trigger: { target: "useCardToTargeted" },
forced: true,
filter(event, player) {
return event.card.name == "sha" && get.color(event.card) == "red";
},
async content(event, trigger, player) {
trigger.getParent().excluded.add(player);
},
},
damage: {
trigger: { source: "damageBegin1" },
forced: true,
filter(event, player) {
return event.card?.name == "sha" && get.color(event.card) == "red";
},
async content(event, trigger, player) {
trigger.num++;
},
},
},
},
};
export default skills;
+28
View File
@@ -0,0 +1,28 @@
const translates = {
zishu_boss_shuying: "子鼠",
zishu_shuying: "子鼠",
zishu_shuying_info: "出牌阶段限一次,你可以获得手牌数大于你的其他角色一张手牌,你可以重复此流程,直到你的手牌数为全场最多。",
chenlong_boss_shuying: "辰龙",
chenlong_shuying: "辰龙",
chenlong_shuying_info: "限定技,出牌阶段,你可以失去任意点体力(至多为5),然后对一名其他角色造成等量的伤害。若你以此法进入濒死状态,则你将体力值回复至1,然后减少1点体力上限。",
wuma_boss_shuying: "午马",
wuma_shuying: "午马",
wuma_shuying_info: "锁定技,你不能被翻面;你的阶段不能被跳过;当你成为其他角色使用锦囊牌的目标后,摸一张牌。",
weiyang_boss_shuying: "未羊",
weiyang_shuying: "未羊",
weiyang_shuying_info: "出牌阶段限一次,你可以弃置任意张不同类型的牌,然后令至多等量角色回复1点体力。",
shenhou_boss_shuying: "申猴",
shenhou_shuying: "申猴",
shenhou_shuying_info: "当你成为【杀】的目标时,你可以进行判定,若结果为红色,则此【杀】对你无效。",
xvgou_boss_shuying: "戌狗",
xvgou_shuying: "戌狗",
xvgou_shuying_info: "锁定技,红色【杀】对你无效;你使用红色【杀】无距离限制且造成伤害+1.",
};
export default translates;
+65
View File
@@ -0,0 +1,65 @@
const characters = {
shuishengonggong_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["tuanliu_shuying", "juehong_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/shuishengonggong_boss_shuying.png"
},
// huoshenzhurong_boss_shuying: {},
baiqi_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["changsheng_shuying", "shashen_shuying", "wuan_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/baiqi_boss_shuying.png"
},
kuafu_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["shenqu_shuying", "lieben_shuying", "yinjiang_shuying", "zhuri_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/kuafu_boss_shuying.png"
},
shaohao_boss_shuying:
{
sex: "male",
group: "shen",
hp: 4,
skills: ["baiyi_shuying", "shenen_shuying",],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/shaohao_boss_shuying.png"
},
xuannv_boss_shuying: {
sex: "female",
group: "shen",
hp: 4,
skills: ["xuanlie_shuying", "jiutian_shuying", "dishi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/xuannv_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3"]
},
hanba_boss_shuying: {
sex: "female",
group: "shen",
hp: 4,
skills: ["xinji_shuying", "zhiri_shuying", "fenshi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/天书乱斗/hanba_boss_shuying.png",
dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/hanba_boss_shuying.mp3"]
},
};
export default characters;
+67
View File
@@ -0,0 +1,67 @@
import { getTianshuDifficulty } from "../../shared.js";
const dynamicTranslates = {
//水神共工
tuanliu_shuying(player, skill) {
let hp = 2;
let card = 4;
let damage = 1;
if (getTianshuDifficulty() == "nightmare") {
hp = 3;
card = 6;
}
return `锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复${hp}点体力;大于5,你摸${card}张牌;大于9,你对所有敌方角色造成${damage}点伤害。`
},
//少昊
baiyi_shuying(player, skill) {
let num = 1;
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
return `锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其${num}张牌;若当前轮数小于5,对其造成${num}点雷电伤害;若当前轮数小于7,其随机弃置${num}张牌。`;
},
//玄女
xuanlie_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "normal" ? 1 : 2;
return `锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成${num}点伤害。`;
},
jiutian_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "normal" ? 1 : 2;
const extra =
difficulty == "normal" || difficulty == "hard"
? ""
: "若这些牌包含4种花色,这些角色再额外失去1点体力。";
return `锁定技,准备阶段,你获得所有敌方角色各${num}张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。${extra}`;
},
xinji_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "nightmare" ? 2 : 1;
return `锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成${num}点伤害。`
},
zhiri_shuying(player, skill) {
let num = 1;
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
return `锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸${num}张牌。`
},
};
export default dynamicTranslates;
+584
View File
@@ -0,0 +1,584 @@
import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js";
const skills = {
//水神共工 over
shuishen_shuying: {
mode: ["boss"],
forced: true,
group: ["shuishen_shuying_recover", "shuishen_shuying_draw", "shuishen_shuying_damage"],
subSkill: {
recover: {
trigger: { player: "recoverBegin" },
audio: false,
forced: true,
filter(event, player) {
return game.shuying_getDiscardNamesNum() >= 4;
},
async content(event, trigger, player) {
trigger.num++;
},
},
draw: {
trigger: { player: "phaseDrawBegin2" },
audio: false,
forced: true,
filter(event, player) {
return !event.numFixed && game.shuying_getDiscardNamesNum() >= 5;
},
async content(event, trigger, player) {
trigger.num += 5;
},
},
damage: {
trigger: { source: "damageBegin1" },
audio: false,
forced: true,
filter(event, player) {
return game.shuying_getDiscardNamesNum() >= 9;
},
async content(event, trigger, player) {
trigger.num++;
},
},
},
},
tuanliu_shuying: {
mode: ["boss"],
trigger: { player: "phaseJieshuBegin" },
audio: false,
forced: true,
filter(event, player) {
return game.shuying_getDiscardNum() > 4;
},
async content(event, trigger, player) {
let hp = 2;
let card = 4;
let damage = 1;
if (getTianshuDifficulty() == "nightmare") {
hp = 3;
card = 6;
}
const num = game.shuying_getDiscardNum();
if (num > 4) await player.recover(hp);
if (num > 5) await player.draw(card);
if (num > 9) {
for (const target of player.getEnemies(null, false).filter(target => target.isIn())) {
player.line(target);
await target.damage(player, damage);
}
}
},
},
juehong_shuying: {
mode: ["boss"],
trigger: { player: "phaseZhunbeiBegin" },
audio: false,
forced: true,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn());
},
async content(event, trigger, player) {
for (const target of player.getEnemies(null, false).filter(target => target.isIn())) {
player.line(target);
if (target.countCards("e")) await target.chooseToDiscard("e", true, target.countCards("e"));
else if (target.countCards("h")) await target.discard(target.getCards("h").randomGets(Math.min(2, target.countCards("h"))));
}
},
},
//白起
changsheng_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
forced: true,
mod: {
targetInRange(card, player, target) {
if (card.name == "sha") return true;
},
},
},
shashen_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
enable: ["chooseToUse", "chooseToRespond"],
filterCard: true,
position: "h",
viewAs: { name: "sha" },
viewAsFilter(player) {
return player.countCards("h") > 0;
},
prompt: "将一张手牌当【杀】使用或打出",
check(card) {
return 6 - get.value(card);
},
group: "shashen_shuying_draw",
subSkill: {
draw: {
trigger: { source: "damageEnd" },
filter(event, player) {
return event.card?.name == "sha";
},
async content(event, trigger, player) {
await player.draw(3);
},
},
},
ai: {
respondSha: true,
skillTagFilter(player) {
return player.countCards("h") > 0;
},
order: 4,
},
},
wuan_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
forced: true,
mod: {
cardUsable(card, player, num) {
if (card.name == "sha") return num + 3;
},
},
trigger: { source: "damageBegin1" },
filter(event, player) {
return event.card?.name == "sha";
},
async content(event, trigger, player) {
trigger.num++;
},
},
//盘古
shenqu_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
trigger: { player: "damageEnd" },
forced: true,
filter(event, player) {
return player.countCards("h", card => get.color(card) == "red") > 0;
},
async content(event, trigger, player) {
const cards = player.getCards("h", card => get.color(card) == "red");
await player.lose(cards, ui.cardPile, "insert");
await player.draw(cards.length);
},
},
lieben_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
trigger: { player: "useCardToPlayered" },
forced: true,
filter(event, player) {
return event.card.name == "sha" && ui.cardPile.lastChild;
},
async content(event, trigger, player) {
const judge = player.judge(card => get.color(card) == "red" ? 2 : -1);
judge.directresult = ui.cardPile.lastChild;
judge.judge2 = result => result.bool;
const result = await judge.forResult();
if (result.bool) {
trigger.getParent().addCount = false;
trigger.getParent().baseDamage++;
}
},
},
yinjiang_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
trigger: { player: "drawEnd" },
forced: true,
filter(event, player) {
return player.isPhaseUsing() && !player.hasSkill("yinjiang_shuying_disabled") && ui.cardPile.lastChild;
},
async content(event, trigger, player) {
const card = ui.cardPile.lastChild;
await player.gain(card, "gain2");
if (get.color(card) == "red") {
let count = 0;
for (const target of player.getEnemies(null, false).filter(target => target.isIn())) {
player.line(target);
await target.damage(player);
count++;
}
player.storage.yinjiang_shuying_count = (player.storage.yinjiang_shuying_count || 0) + count;
if (player.storage.yinjiang_shuying_count >= 2) player.addTempSkill("yinjiang_shuying_disabled", { player: "phaseUseEnd" });
}
},
group: "yinjiang_shuying_clear",
subSkill: {
clear: {
trigger: { player: "phaseUseEnd" },
silent: true,
async content(event, trigger, player) {
delete player.storage.yinjiang_shuying_count;
},
},
disabled: { charlotte: true },
},
},
zhuri_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill:1",
forced: true,
group: ["zhuri_shuying_draw", "zhuri_shuying_bottom"],
subSkill: {
draw: {
trigger: { player: "useCard" },
forced: true,
filter(event, player) {
return get.color(event.card) == "red";
},
async content(event, trigger, player) {
await player.draw();
},
},
bottom: {
trigger: { global: "useCardAfter" },
forced: true,
filter(event, player) {
return event.targets?.includes(player) && (event.card.name == "sha" || get.type(event.card) == "trick") && event.cards?.filterInD("d").length;
},
async content(event, trigger, player) {
const cards = trigger.cards.filterInD("d");
game.log(cards, "被置于牌堆底");
for (const card of cards) ui.cardPile.appendChild(card);
},
},
},
},
//少昊 over
baiyi_shuying: {
mode: ["boss"],
trigger: { global: "phaseBegin" },
forced: true,
logTarget: "player",
filter(event, player) {
return (
game.roundNumber < 7 &&
event.player?.isIn() &&
player.getEnemies(null, false).includes(event.player)
);
},
async content(event, trigger, player) {
let num = 1;
const target = trigger.player;
player.line(target);
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
if (game.roundNumber < 3 && target.countGainableCards(player, "he") > 0) {
const cards = target.getCards("he").randomGets(Math.min(num, target.countCards("he")));
await player.gain(cards, target, "giveAuto");
}
if (game.roundNumber < 5 && target.isIn()) {
await target.damage(num, "thunder", player);
}
if (game.roundNumber < 7 && target.isIn()) {
const cards = target
.getCards("he")
.filter(card => lib.filter.cardDiscardable(card, target, "baiyi_shuying"))
.randomGets(num);
if (cards.length) {
await target.discard(cards);
}
}
},
ai: {
threaten: 3,
},
},
shenen_shuying: {
mode: ["boss"],
forced: true,
global: "shenen_shuying_global",
subSkill: {
global: {
mod: {
targetInRange(card, player, target) {
const owner = game.findPlayer(current => current.hasSkill("shenen_shuying"));
if (owner && !owner.getEnemies(null, false).includes(player)) return true;
},
},
},
},
ai: {
threaten: 1.5,
},
},
//玄女 over
xuanlie_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:1",
trigger: { player: "phaseJieshuBegin" },
forced: true,
locked: true,
filter(event, player) {
return game.hasPlayer(current => {
return (
current.isIn() &&
player.getEnemies(null, false).includes(current) &&
player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length
);
});
},
async content(event, trigger, player) {
const targets = game.filterPlayer(current => {
return (
current.isIn() &&
player.getEnemies(null, false).includes(current) &&
player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length
);
});
let num = 2;
if (getTianshuDifficulty() == "normal") num = 1;
for (const target of targets) {
player.line(target);
await target.damage(num, player);
}
},
ai: {
threaten: 2,
},
},
jiutian_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:1",
trigger: { player: "phaseZhunbeiBegin" },
forced: true,
locked: true,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn() && target.countGainableCards(player, "h") > 0);
},
async content(event, trigger, player) {
let num = 2;
const difficulty = getTianshuDifficulty();
if (difficulty == "normal") num = 1;
const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countGainableCards(player, "h") > 0);
const gained = [];
for (const target of targets) {
const cards = target.getCards("h").randomGets(Math.min(num, target.countCards("h")));
if (cards.length) {
player.line(target);
await player.gain(cards, target, "giveAuto");
gained.addArray(cards);
}
}
if (!gained.length) return;
const colors = gained.map(card => get.color(card, player)).unique();
const suits = gained.map(card => get.suit(card, player)).unique();
if (colors.length >= 2) {
for (const target of targets) {
if (!target.isIn()) continue;
player.line(target);
target.damage(1, player);
}
}
if (difficulty == "nightmare" && suits.length >= 4) {
for (const target of targets) {
if (!target.isIn()) continue;
player.line(target);
await target.loseHp();
}
}
},
ai: {
threaten: 3,
},
},
dishi_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:1",
trigger: { player: "useCard2" },
filter(event, player) {
if (!event.targets?.length) return false;
if (event.card.name != "sha" && get.type(event.card) != "trick") return false;
const info = get.info(event.card);
if (event.targets.length == 1) {
if (info.allowMultiple == false || info.multitarget) return false;
return game.hasPlayer(current => {
return (
!event.targets.includes(current) &&
lib.filter.targetEnabled2(event.card, player, current) &&
lib.filter.targetInRange(event.card, player, current)
);
});
}
return event.targets.length > 1;
},
async cost(event, trigger, player) {
if (trigger.targets.length == 1) {
event.result = await player
.chooseTarget(get.prompt(event.skill), `为${get.translation(trigger.card)}增加一个目标`, (card, player, target) => {
const trigger = get.event().getTrigger();
return (
!trigger.targets.includes(target) &&
lib.filter.targetEnabled2(trigger.card, player, target) &&
lib.filter.targetInRange(trigger.card, player, target)
);
})
.set("ai", target => {
const player = get.player();
const trigger = get.event().getTrigger();
const effect = get.effect(target, trigger.card, player, player);
if (player.getEnemies(null, false).includes(target)) return effect;
return Math.min(0, effect);
})
.forResult();
}
else {
event.result = await player
.chooseTarget(get.prompt(event.skill), `令${get.translation(trigger.card)}减少一个目标`, (card, player, target) => {
return get.event().getTrigger().targets.includes(target);
})
.set("ai", target => {
const player = get.player();
const trigger = get.event().getTrigger();
return -get.effect(target, trigger.card, player, player);
})
.forResult();
}
},
async content(event, trigger, player) {
const target = event.targets[0];
player.line(target);
if (trigger.targets.includes(target)) {
trigger.targets.remove(target);
game.log(target, "不再是", trigger.card, "的目标");
}
else {
trigger.targets.add(target);
game.log(target, "成为了", trigger.card, "的额外目标");
}
},
ai: {
threaten: 1.6,
},
},
//旱魃
xinji_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:2",
trigger: { global: "loseAfter" },
forced: true,
locked: true,
filter(event, player) {
return (
_status.currentPhase &&
_status.currentPhase.isIn() &&
event.player?.isIn() &&
event.player != _status.currentPhase &&
!player.getEnemies(null, false).includes(event.player) &&
event.type == "discard" &&
event.cards2?.some(card => get.position(card, true) == "d" && event.hs?.includes(card))
);
},
async content(event, trigger, player) {
const difficulty = getTianshuDifficulty();
const num = difficulty == "nightmare" ? 2 : 1;
const target = _status.currentPhase;
player.line(target);
await target.damage(num, player);
},
ai: {
threaten: 1.6,
},
},
zhiri_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:2",
trigger: { global: "useCardToPlayered" },
forced: true,
locked: true,
filter(event, player) {
return (
event.isFirstTarget &&
event.player?.isIn() &&
player.getEnemies(null, false).includes(event.player) &&
get.type2(event.card) == "trick" &&
get.color(event.card) == "red"
);
},
async content(event, trigger, player) {
let num = 1;
const difficulty = getTianshuDifficulty();
if (difficulty == "hard") {
num = 2;
} else if (difficulty == "nightmare") {
num = 3;
}
await player.draw(num);
},
ai: {
threaten: 1.4,
},
},
fenshi_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/天书乱斗:2",
trigger: { player: "phaseZhunbeiBegin" },
forced: true,
locked: true,
filter(event, player) {
const num = player.countCards("h") - player.hp;
if (num < 0) return true;
if (num > 0) return player.getEnemies(null, false).some(target => target.isIn());
return false;
},
async content(event, trigger, player) {
const num = player.countCards("h") - player.hp;
if (num < 0) {
await player.draw(-num);
return;
}
const enemies = player.getEnemies(null, false).filter(target => target.isIn());
const map = new Map();
for (let i = 0; i < num && enemies.length; i++) {
const target = enemies.randomGet();
map.set(target, (map.get(target) || 0) + 1);
}
for (const [target, damage] of map) {
if (!target.isIn()) continue;
player.line(target);
await target.damage(damage, player);
}
},
ai: {
threaten: 2,
},
},
};
export default skills;
+53
View File
@@ -0,0 +1,53 @@
const translates = {
shuishengonggong_boss_shuying: "水神共工",
shuishen_shuying: "水神",
shuishen_shuying_info: "锁定技,弃牌堆中,不同牌名的牌数达到4时,你回复体力时回复量+1;达到5时,摸牌阶段额外摸5张牌;达到9时,你造成伤害时伤害+1。",
tuanliu_shuying: "湍流",
tuanliu_shuying_info: "锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复2点体力;大于5,你摸4张牌;大于9,你对所有敌方角色造成1点伤害。",
juehong_shuying: "决洪",
juehong_shuying_info: "锁定技,准备阶段,你令所有敌方角色自己弃置自己的装备区内的所有牌,若其装备区内没有牌,则改为随机弃置2张手牌",
baiqi_boss_shuying: "白起",
changsheng_shuying: "常胜",
changsheng_shuying_info: "锁定技,你使用【杀】无距离限制。",
shashen_shuying: "杀神",
shashen_shuying_info: "你可以将手牌中的任意牌当【杀】使用或打出。你使用的【杀】造成伤害后,摸三张牌。",
wuan_shuying: "武安",
wuan_shuying_info: "锁定技,你可使用的【杀】的次数+3,【杀】造成的伤害+1。",
kuafu_boss_shuying: "盘古",
shenqu_shuying: "神躯",
shenqu_shuying_info: "锁定技,当你受到伤害后,将所有红色手牌置于牌堆底,并摸等量的牌。",
lieben_shuying: "烈奔",
lieben_shuying_info: "锁定技,当你使用【杀】指定目标后,使用牌堆底的牌进行一次判定:若判定结果为红色,则此杀不计入出牌阶段使用次数且伤害+1。",
yinjiang_shuying: "饮江",
yinjiang_shuying_info: "锁定技,当你在出牌阶段摸牌后,获得牌堆底的1张牌。若该牌为红色,则对所有敌方角色造成1点伤害。当你在同一个阶段内以此法造成过两次活更多的伤害后,该技能失效直到回合结束。",
zhuri_shuying: "逐日",
zhuri_shuying_info: "锁定技,你使用红色牌时摸1张牌。当你成为【杀】或普通锦囊牌的目标后,结算完毕后将该牌放置在牌堆底。",
shaohao_boss_shuying: "少昊",
baiyi_shuying: "白仪",
baiyi_shuying_info: "锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其1张牌;若当前轮数小于5,对其造成1点雷电伤害;若当前轮数小于7,其随机弃置1张牌。",
shenen_shuying: "神恩",
shenen_shuying_info: "锁定技,所有己方角色使用牌锁定技,所有己方角色使用牌没有距离限制。",
xuannv_boss_shuying: "玄女",
xuanlie_shuying: "玄烈",
xuanlie_shuying_info: "锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成1点伤害。",
jiutian_shuying: "九天",
jiutian_shuying_info: "锁定技,准备阶段,你获得所有敌方角色各1张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。",
dishi_shuying: "帝师",
dishi_shuying_info: "当你使用【杀】或普通锦囊牌指定目标时,如果目标数为1,可以增加一个目标;如果目标数大于1,你可以减少一个目标。",
hanba_boss_shuying: "旱魃",
xinji_shuying: "心悸",
xinji_shuying_info: "锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成1点伤害。",
zhiri_shuying: "炙日",
zhiri_shuying_info: "锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸1张牌。",
fenshi_shuying: "焚世",
fenshi_shuying_info: "锁定技,准备阶段,若你的手牌数小于体力值,则将手牌摸至于体力值相等;若你的手牌数大于体力值,则你对敌方角色造成共计X点伤害,点数随机分配(X为手牌数减体力值)。",
};
export default translates;
+50
View File
@@ -0,0 +1,50 @@
const characters = {
heibaiwuchang_boss_shuying:
{
sex: "male",
group: "qun",
hp: 4,
skills: ["mizui_shuying", "qiangzheng_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png"
},
niutoumamian_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["shiyv_shuying", "manji_shuying", "xiaoshou_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png"
},
yvsai_boss_shuying: {
sex: "female",
group: "qun",
hp: 4,
skills: ["anchao_shuying", "guixi_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/yvsai_boss_shuying.png"
},
huangfeng_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["mingchong_shuying", "duzhen_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png"
},
riyeyoushen_boss_shuying: {
sex: "male",
group: "qun",
hp: 4,
skills: ["huiyun_shuying", "yezhong_shuying", "zhoucha_shuying"],
isHiddenBoss: true,
isBossAllowed: true,
img: "extension/术樱包/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png"
},
};
export default characters;
@@ -0,0 +1,74 @@
import { getTianshuDifficulty } from "../../shared.js";
const dynamicTranslates = {
//黑白无常
mizui_shuying(player, skill) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2
return `锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的${num}张牌。`
},
qiangzheng_shuying(player, skill) {
let num = 2;
if (getTianshuDifficulty() == "hard") num = 4;
if (getTianshuDifficulty() == "nightmare") num = 6;
return `锁定技,敌方角色的结束阶段,若其手牌数小于${num},你获得其所有手牌。`
},
xixing_shuying(player, skill) {
const difficulty = getTianshuDifficulty();
if (difficulty == "nightmare") {
return "锁定技,准备阶段,你对每名敌方角色各造成2点雷电伤害,然后你回复等量体力";
}
return "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力。";
},
//鱼鳃
guixi_shuying(player, skill) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1
return `锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸${num}张牌。`;
},
//黄蜂
mingchong_shuying(player, skill) {
if (getTianshuDifficulty() == "nightmare") {
return "锁定技,你死亡时,杀死你的角色弃置所有牌。"
}
return "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。"
},
duzhen_shuying(player, skill) {
if (getTianshuDifficulty() == "nightmare") {
return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。"
}
return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置2张牌(优先装备区的牌)。"
},
//日夜游神
huiyun_shuying(player, skill) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2
return `出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成${num}点伤害。`
},
yezhong_shuying(player, skill) {
let num = 1;
if (getTianshuDifficulty() == "hard") num = 2;
if (getTianshuDifficulty() == "nightmare") num = 3;
return `锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃${num}张手牌。`
},
zhoucha_shuying(player, skill) {
let num = 1;
if (getTianshuDifficulty() == "hard") num = 2;
if (getTianshuDifficulty() == "nightmare") num = 3;
return `锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+${num}。`
},
};
export default dynamicTranslates;
+570
View File
@@ -0,0 +1,570 @@
import { lib, game, ui, get, ai, _status, getTianshuDifficulty } from "../../shared.js";
const skills = {
//黑白无常 over
mizui_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { source: "damageEnd" },
forced: true,
filter(event, player) {
return event.player?.isIn() && event.player.countDiscardableCards(player, "he") > 0 && event.card && get.is.damageCard(event.card);
},
async content(event, trigger, player) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2;
player.line(trigger.player);
await player.discardPlayerCard(trigger.player, num, "he", true);
},
ai: { expose: 0.2 },
},
qiangzheng_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { global: "phaseEnd" },
forced: true,
logTarget: "player",
filter(event, player) {
let num = 2;
if (getTianshuDifficulty() == "hard") num = 4;
if (getTianshuDifficulty() == "nightmare") num = 6;
return event.player?.isIn() && player.getEnemies(null, false).includes(event.player) && event.player.countCards("h") > 0 && event.player.countCards("h") < num;
},
async content(event, trigger, player) {
player.line(trigger.player);
await player.gain(trigger.player.getCards("h"), trigger.player, "giveAuto");
},
},
xixing_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "phaseZhunbeiBegin" },
forced: true,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn());
},
async content(event, trigger, player) {
const enemies = player.getEnemies(null, false).filter(target => target.isIn());
if (difficulty == "nightmare") {
for (const target of enemies) {
player.line(target, "thunder");
await target.damage(2, "thunder", player);
}
} else {
const max = Math.max(...enemies.map(target => target.hp));
const target = enemies.filter(target => target.hp == max).randomGet();
player.line(target, "thunder");
await target.damage(2, "thunder", player);
}
await player.recover(2);
},
},
taiping_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "damageEnd" },
forced: true,
locked: true,
logTarget: "source",
filter(event, player) {
return (
event.num > 0 &&
event.source?.isIn() &&
player.getEnemies(null, false).includes(event.source)
);
},
async content(event, trigger, player) {
const source = trigger.source;
player.line(source);
const result = await source
.chooseToDiscard("h", 2, `太平:弃置两张花色不同的手牌,否则失去1点体力`, card => {
const cards = ui.selected.cards;
return !cards.some(cardx => get.suit(cardx) == get.suit(card));
})
.set("ai", card => {
const player = get.player();
if (player.hp <= 1) return 8 - get.value(card);
return 6 - get.value(card);
})
.forResult();
if (!result.bool) {
await source.loseHp();
}
},
ai: {
maixie_defend: true,
effect: {
target(card, player, target) {
if (get.tag(card, "damage") && target.getEnemies(null, false).includes(player)) {
if (player.countCards("h") < 2) return [1, -1];
return [1, 0.4];
}
},
},
},
},
//牛头马面
shiyv_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "phaseDrawBegin2" },
forced: true,
filter(event, player) {
return !event.numFixed;
},
async content(event, trigger, player) {
trigger.num = 0;
const cards = [];
for (const suit of lib.suit) {
const card = get.cardPile(card => get.suit(card) == suit, "cardPile", "random");
if (card) cards.push(card);
}
if (cards.length) await player.gain(cards, "gain2");
},
},
manji_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "useCardToPlayered" },
filter(event, player) {
return event.card.name == "sha" && event.targets?.length == 1 && player.getEnemies(null, false).includes(event.target) && event.target.countDiscardableCards(player, "h") > 0;
},
async cost(event, trigger, player) {
event.result = await player.chooseBool(get.prompt(event.skill, trigger.target))
.set("ai", () => get.attitude(get.player(), get.event().getTrigger().target) < 0)
.forResult();
},
async content(event, trigger, player) {
const target = trigger.target;
player.line(target);
const cards = target.getCards("h").randomGets(Math.min(2, target.countCards("h")));
await target.discard(cards);
if (cards.some(card => get.name(card, target) == "sha")) trigger.getParent().baseDamage++;
},
ai: { expose: 0.2 },
},
xiaoshou_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "phaseZhunbeiBegin" },
forced: true,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn());
},
async content(event, trigger, player) {
const target = player.getEnemies(null, false).filter(target => target.isIn()).randomGet();
player.line(target);
await target.damage(2, player);
},
},
//鱼鳃 over
anchao_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
forced: true,
locked: true,
group: ["anchao_shuying_gain", "anchao_shuying_start"],
marktext: "潮",
intro: {
name: "暗潮",
content: "mark",
},
subSkill: {
gain: {
audio: "anchao_shuying",
trigger: { global: "phaseJieshuBegin" },
forced: true,
logTarget: "player",
filter(event, player) {
return (
event.player?.isIn() &&
!player.getEnemies(null, false).includes(event.player) &&
!event.player.getHistory("sourceDamage", evt => evt.num > 0).length
);
},
async content(event, trigger, player) {
trigger.player.addMark("anchao_shuying", 1);
},
},
start: {
audio: "anchao_shuying",
trigger: { global: "phaseBegin" },
forced: true,
logTarget: "player",
filter(event, player) {
return (
event.player?.isIn() &&
!player.getEnemies(null, false).includes(event.player) &&
event.player.countMark("anchao_shuying") > 0
);
},
async content(event, trigger, player) {
const target = trigger.player;
target.storage.anchao_shuying_effect = target.countMark("anchao_shuying");
target.addTempSkill("anchao_shuying_effect", { player: "phaseAfter" });
},
},
effect: {
charlotte: true,
group: ["anchao_shuying_draw", "anchao_shuying_damage"],
onremove(player) {
delete player.storage.anchao_shuying_effect;
},
},
draw: {
audio: "anchao_shuying",
trigger: { player: "phaseDrawBegin2" },
forced: true,
filter(event, player) {
return !event.numFixed && player.storage.anchao_shuying_effect > 0;
},
async content(event, trigger, player) {
trigger.num += player.storage.anchao_shuying_effect;
},
},
damage: {
audio: "anchao_shuying",
trigger: { source: "damageBegin1" },
forced: true,
filter(event, player) {
return (
player.storage.anchao_shuying_effect > 0 &&
event.player?.isIn() &&
player.getEnemies(null, false).includes(event.player)
);
},
async content(event, trigger, player) {
trigger.num += player.storage.anchao_shuying_effect;
},
},
},
ai: {
threaten: 2,
},
},
guixi_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "damageEnd" },
forced: true,
locked: true,
filter(event, player) {
return event.num > 0;
},
async content(event, trigger, player) {
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1;
const result = await player
.judge(card => get.color(card) == "red" ? 2 : 0)
.set("judge2", result => result.bool)
.forResult();
if (result.bool && player.isDamaged()) {
await player.recover();
}
else {
await player.draw(num);
}
},
ai: {
maixie: true,
maixie_hp: true,
effect: {
target(card, player, target) {
if (get.tag(card, "damage")) {
if (player.hasSkillTag("jueqing", false, target)) return [1, -1];
return [1, 0.7];
}
},
},
},
},
//黄蜂 over
mingchong_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "die" },
forced: true,
locked: true,
forceDie: true,
logTarget: "source",
filter(event, player) {
return event.source?.isIn() && event.source.countDiscardableCards(player, "he") > 0;
},
async content(event, trigger, player) {
const source = trigger.source;
player.line(source);
const cards = source
.getCards("he")
.filter(card => lib.filter.cardDiscardable(card, source, "mingchong_shuying"));
if (cards.length) {
if (getTianshuDifficulty() == "nightmare") {
await source.discard(cards);
}
else {
await source.discard(cards.randomGets(Math.ceil(cards.length / 2)));
}
}
},
ai: {
threaten: 0.8,
effect: {
target(card, player, target) {
if (get.tag(card, "damage") && target.hp <= 1 && player.countCards("he") > 2) {
return [1, -0.8];
}
},
},
},
},
duzhen_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "useCardToPlayered" },
forced: true,
locked: true,
logTarget: "target",
filter(event, player) {
return (
event.targets?.length == 1 &&
event.target?.isIn() &&
player.getEnemies(null, false).includes(event.target) &&
event.target.countDiscardableCards(player, "he") > 0
);
},
async content(event, trigger, player) {
const target = trigger.target;
const num = getTianshuDifficulty() == "nightmare" ? 2 : 1;
const cards = [];
player.line(target);
const equips = target
.getCards("e")
.filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying"))
.randomGets(num);
cards.addArray(equips);
if (cards.length < num) {
const hands = target
.getCards("h")
.filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying"))
.randomGets(num - cards.length);
cards.addArray(hands);
}
if (cards.length) {
await target.discard(cards);
}
},
ai: {
threaten: 1.8,
effect: {
player_use(card, player, target) {
if (target && player.getEnemies(null, false).includes(target) && target.countCards("he") > 0) {
return [1, 0.5];
}
},
},
},
},
//日夜游神 over
huiyun_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
enable: "phaseUse",
usable: 1,
filter(event, player) {
return player.getEnemies(null, false).some(target => target.isIn() && target.countCards("h") > 0);
},
filterTarget(card, player, target) {
return target.isIn() && player.getEnemies(null, false).includes(target) && target.countCards("h") > 0;
},
async content(event, trigger, player) {
const target = event.targets[0];
const hs = target.getCards("h");
player.line(target);
await target.showCards(hs, `${get.translation(player)}对${get.translation(target)}发动了【${get.translation(event.name)}】`);
const result = await player
.choosePlayerCard(target, "h", [1, 2], `弃置${get.translation(target)}至多两张手牌`)
.set("filterButton", button => {
const player = get.player();
const target = get.event().target;
return lib.filter.canBeDiscarded(button.link, player, target);
})
.set("ai", button => {
const card = button.link;
const player = get.player();
const target = get.event().target;
let value = get.value(card, target);
if (get.name(card, target) == "tao") value += 3;
if (get.name(card, target) == "shan") value += 1;
return value;
})
.set("target", target)
.forResult();
if (!result.bool || !result.cards?.length) return;
const names = result.cards.map(card => get.name(card, target)).unique();
await target.discard(result.cards);
if (!target.isIn()) return;
const result2 = await player
.chooseToDiscard("he", `是否弃置一张同名牌,对${get.translation(target)}造成2点伤害?`, card => {
return get.event().names.includes(get.name(card));
})
.set("names", names)
.set("ai", card => {
const player = get.player();
const target = get.event().target;
if (!player.getEnemies(null, false).includes(target)) return 0;
const effect = get.damageEffect(target, player, player);
if (effect <= 0) return 0;
return effect * 3 - get.value(card);
})
.set("target", target)
.forResult();
if (result2.bool) {
const num = getTianshuDifficulty() == "normal" ? 1 : 2
player.line(target);
await target.damage(num, player);
}
},
ai: {
order: 5,
result: {
target(player, target) {
if (!player.getEnemies(null, false).includes(target)) return 0;
return -Math.min(2, target.countCards("h"));
},
},
threaten: 1.6,
},
},
yezhong_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "phaseJieshuBegin" },
forced: true,
locked: true,
async content(event, trigger, player) {
const result = await player
.judge(card => get.color(card) == "black" ? 2 : -1)
.set("judge2", result => result.bool)
.forResult();
if (result.card) {
await player.gain(result.card, "gain2");
}
if (result.bool) {
let num = 1;
if (getTianshuDifficulty() == "hard") num = 2;
if (getTianshuDifficulty() == "nightmare") num = 3;
const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countCards("h") > 0);
for (const target of targets) {
player.line(target);
const cards = target
.getCards("h")
.filter(card => lib.filter.cardDiscardable(card, target, "yezhong_shuying"))
.randomGets(num);
if (cards.length) {
await target.discard(cards);
}
}
}
},
ai: {
threaten: 1.8,
},
},
zhoucha_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { player: "phaseZhunbeiBegin" },
forced: true,
locked: true,
async content(event, trigger, player) {
const result = await player
.judge(card => get.color(card) == "red" ? 2 : -1)
.set("judge2", result => result.bool)
.forResult();
if (result.card) {
await player.gain(result.card, "gain2");
}
if (result.bool) {
player.addTempSkill("zhoucha_shuying_effect", { player: "phaseAfter" });
}
},
subSkill: {
effect: {
charlotte: true,
mod: {
cardUsable(card, player, num) {
let number = 1;
if (getTianshuDifficulty() == "hard") number = 2;
if (getTianshuDifficulty() == "nightmare") number = 3;
if (card.name == "sha") return num + number;
},
},
},
},
ai: {
threaten: 1.4,
},
},
duane_shuying: {
mode: ["boss"],
audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1",
trigger: { global: "phaseDiscardEnd" },
forced: true,
locked: true,
logTarget: "player",
filter(event, player) {
const target = event.player;
return (
target?.isIn() &&
player.getEnemies(null, false).includes(target) &&
target.getHistory("lose", evt => {
return (
evt.type == "discard" &&
evt.getParent("phaseDiscard") == event &&
evt.cards2?.some(card => get.color(card, target) == "black")
);
}).length
);
},
async content(event, trigger, player) {
const target = trigger.player;
player.line(target);
await target.loseHp(2);
},
ai: {
threaten: 1.5,
},
},
};
export default skills;
+45
View File
@@ -0,0 +1,45 @@
const translates = {
heibaiwuchang_boss_shuying: "黑白无常",
mizui_shuying: "迷醉",
mizui_shuying_info: "锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的1张牌。",
qiangzheng_shuying: "强征",
qiangzheng_shuying_info: "锁定技,敌方角色的结束阶段,若其手牌数小于4,你获得其所有手牌。",
xixing_shuying: "吸星",
xixing_shuying_info: "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力",
taiping_shuying: "太平",
taiping_shuying_info: "锁定技,当你受到敌方角色造成的伤害后,伤害来源须弃置两张花色不同的手牌,否则失去1点体力。",
niutoumamian_boss_shuying: "牛头马面",
shiyv_shuying: "施狱",
shiyv_shuying_info: "锁定技,摸牌阶段,你放弃摸牌改为随机获得牌堆中每种花色的牌各一张。",
manji_shuying: "蛮击",
manji_shuying_info: "你使用【杀】指定单一目标后,你可以弃置该角色2张手牌。若其中有【杀】,你本次【杀】造成的伤害+1。",
xiaoshou_shuying: "枭首",
xiaoshou_shuying_info: "锁定技,准备阶段,你对随机一名敌方角色造成2点伤害",
yvsai_boss_shuying: "鱼鳃",
anchao_shuying: "暗潮",
anchao_shuying_info: "锁定技,己方角色的回合结束时,若此回合该角色没有造成过伤害,则获得一个“暗潮”标记。己方回合开始时,若该角色有“暗潮”标记,则本回合摸牌阶段多摸X张牌且对敌方角色造成的伤害+X(X为“暗潮”标记数)",
guixi_shuying: "鬼吸",
guixi_shuying_info: "锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸1张牌。",
huangfeng_boss_shuying: "黄蜂",
mingchong_shuying: "冥虫",
mingchong_shuying_info: "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。",
duzhen_shuying: "毒针",
duzhen_shuying_info: "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。",
riyeyoushen_boss_shuying: "日夜游神",
huiyun_shuying: "晦运",
huiyun_shuying_info: "出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成2点伤害。",
yezhong_shuying: "夜冢",
yezhong_shuying_info: "锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃1张手牌。",
zhoucha_shuying: "昼刹",
zhoucha_shuying_info: "锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+1。",
duane_shuying: "断恶",
duane_shuying_info: "锁定技,敌方角色的弃牌阶段结束时,若其此阶段弃置了黑色牌,则该角色失去2点体力。",
};
export default translates;
+9
View File
@@ -0,0 +1,9 @@
import { lib, game, ui, get, ai, _status } from "../../../../noname.js";
import tianshuConfig from "../../tianshu/config.js";
// 统一提供活动 BOSS 技能依赖与天书难度,避免各主题模块重复维护加载路径。
const getTianshuDifficulty = () => {
return _status[tianshuConfig.settings.difficultyStatusKey] || "normal";
};
export { lib, game, ui, get, ai, _status, getTianshuDifficulty };
+13 -2116
View File
File diff suppressed because it is too large Load Diff
+12 -150
View File
@@ -1,153 +1,15 @@
const translates = {
zishu_boss_shuying: "子鼠",
zishu_shuying: "子鼠",
zishu_shuying_info: "出牌阶段限一次,你可以获得手牌数大于你的其他角色一张手牌,你可以重复此流程,直到你的手牌数为全场最多。",
import shiershengxiao from "./sets/shiershengxiao/translate.js";
import zhuoguiquxie from "./sets/zhuoguiquxie/translate.js";
import qingqingzijin from "./sets/qingqingzijin/translate.js";
import tianshu from "./sets/tianshu/translate.js";
import mergeBossModules from "./merge.js";
chenlong_boss_shuying: "辰龙",
chenlong_shuying: "辰龙",
chenlong_shuying_info: "限定技,出牌阶段,你可以失去任意点体力(至多为5),然后对一名其他角色造成等量的伤害。若你以此法进入濒死状态,则你将体力值回复至1,然后减少1点体力上限。",
wuma_boss_shuying: "午马",
wuma_shuying: "午马",
wuma_shuying_info: "锁定技,你不能被翻面;你的阶段不能被跳过;当你成为其他角色使用锦囊牌的目标后,摸一张牌。",
weiyang_boss_shuying: "未羊",
weiyang_shuying: "未羊",
weiyang_shuying_info: "出牌阶段限一次,你可以弃置任意张不同类型的牌,然后令至多等量角色回复1点体力。",
shenhou_boss_shuying: "申猴",
shenhou_shuying: "申猴",
shenhou_shuying_info: "当你成为【杀】的目标时,你可以进行判定,若结果为红色,则此【杀】对你无效。",
xvgou_boss_shuying: "戌狗",
xvgou_shuying: "戌狗",
xvgou_shuying_info: "锁定技,红色【杀】对你无效;你使用红色【杀】无距离限制且造成伤害+1.",
heibaiwuchang_boss_shuying: "黑白无常",
mizui_shuying: "迷醉",
mizui_shuying_info: "锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的1张牌。",
qiangzheng_shuying: "强征",
qiangzheng_shuying_info: "锁定技,敌方角色的结束阶段,若其手牌数小于4,你获得其所有手牌。",
xixing_shuying: "吸星",
xixing_shuying_info: "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力",
niutoumamian_boss_shuying: "牛头马面",
shiyv_shuying: "施狱",
shiyv_shuying_info: "锁定技,摸牌阶段,你放弃摸牌改为随机获得牌堆中每种花色的牌各一张。",
manji_shuying: "蛮击",
manji_shuying_info: "你使用【杀】指定单一目标后,你可以弃置该角色2张手牌。若其中有【杀】,你本次【杀】造成的伤害+1。",
xiaoshou_shuying: "枭首",
xiaoshou_shuying_info: "锁定技,准备阶段,你对随机一名敌方角色造成2点伤害",
yvsai_boss_shuying: "鱼鳃",
anchao_shuying: "暗潮",
anchao_shuying_info: "锁定技,己方角色的回合结束时,若此回合该角色没有造成过伤害,则获得一个“暗潮”标记。己方回合开始时,若该角色有“暗潮”标记,则本回合摸牌阶段多摸X张牌且对敌方角色造成的伤害+X(X为“暗潮”标记数)",
guixi_shuying: "鬼吸",
guixi_shuying_info: "锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸1张牌。",
huangfeng_boss_shuying: "黄蜂",
mingchong_shuying: "冥虫",
mingchong_shuying_info: "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。",
duzhen_shuying: "毒针",
duzhen_shuying_info: "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。",
riyeyoushen_boss_shuying: "日夜游神",
huiyun_shuying: "晦运",
huiyun_shuying_info: "出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成2点伤害。",
yezhong_shuying: "夜冢",
yezhong_shuying_info: "锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃1张手牌。",
zhoucha_shuying: "昼刹",
zhoucha_shuying_info: "锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+1。",
duane_shuying: "断恶",
duane_shuying_info: "锁定技,敌方角色的弃牌阶段结束时,若其此阶段弃置了黑色牌,则该角色失去2点体力。",
caocao_boss_shuying: "曹操",
jianxiong_shuying: "奸雄",
jianxiong_shuying_info: "当你受到伤害后,你可以获得对你造成伤害的牌并摸一张牌。",
lingba_shuying: "凌霸",
lingba_shuying_info: "锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成1点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。",
yishen_shuying: "疑神",
yishen_shuying_info: "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。",
simayi_boss_shuying: "司马懿",
fankui_shuying: "反馈",
fankui_shuying_info: "当你受到1点伤害后,你可以获得伤害来源的一张牌。",
guicai_shuying: "鬼才",
guicai_shuying_info: "当一名角色的判定牌生效前,你可以打出一张牌代替之。",
langgu_shuying: "狼顾",
langgu_shuying_info: " 锁定技,每回合限一次,当你获得其他角色的牌时,进行一次判定:若结果为黑桃,随机弃置其1张手牌,且视为此技能本回合未发动过。",
yuanlv_shuying: "远虑",
yuanlv_shuying_info: "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。",
lvbu_boss_shuying: "吕布",
wushuang_shuying: "无双",
wushuang_shuying_info: "锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限1次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。",
shenij_shuying: "神戟",
shenij_shuying_info: "判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸两张牌;出牌阶段,你可以多使用一张【杀】,你的【杀】可以多指定一名角色为目标。",
zhanjia_shuying: "战甲",
zhanjia_shuying_info: "锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸两张牌。",
dongzhuo_boss_shuying: "董卓",
jiuchi_shuying: "酒池",
jiuchi_shuying_info: "你可以将一张黑桃手牌当【酒】使用。",
roulin_shuying: "肉林",
roulin_shuying_info: "锁定技,你对女性角色使用的【杀】和女性角色对你使用的【杀】均需使用两张【闪】才能抵消。",
baonue_shuying: "暴虐",
baonue_shuying_info: "锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为3。",
qubu_shuying: "驭布",
qubu_shuying_info: "锁定技,当友方角色使用【杀】指定目标时,进行一次判定:若结果为黑桃,你对此【杀】的所有目标造成1点伤害。",
shuishengonggong_boss_shuying: "水神共工",
shuishen_shuying: "水神",
shuishen_shuying_info: "锁定技,弃牌堆中,不同牌名的牌数达到4时,你回复体力时回复量+1;达到5时,摸牌阶段额外摸5张牌;达到9时,你造成伤害时伤害+1。",
tuanliu_shuying: "湍流",
tuanliu_shuying_info: "锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复2点体力;大于5,你摸4张牌;大于9,你对所有敌方角色造成1点伤害。",
juehong_shuying: "决洪",
juehong_shuying_info: "锁定技,准备阶段,你令所有敌方角色自己弃置自己的装备区内的所有牌,若其装备区内没有牌,则改为随机弃置2张手牌",
baiqi_boss_shuying: "白起",
changsheng_shuying: "常胜",
changsheng_shuying_info: "锁定技,你使用【杀】无距离限制。",
shashen_shuying: "杀神",
shashen_shuying_info: "你可以将手牌中的任意牌当【杀】使用或打出。你使用的【杀】造成伤害后,摸三张牌。",
wuan_shuying: "武安",
wuan_shuying_info: "锁定技,你可使用的【杀】的次数+3,【杀】造成的伤害+1。",
kuafu_boss_shuying: "盘古",
shenqu_shuying: "神躯",
shenqu_shuying_info: "锁定技,当你受到伤害后,将所有红色手牌置于牌堆底,并摸等量的牌。",
lieben_shuying: "烈奔",
lieben_shuying_info: "锁定技,当你使用【杀】指定目标后,使用牌堆底的牌进行一次判定:若判定结果为红色,则此杀不计入出牌阶段使用次数且伤害+1。",
yinjiang_shuying: "饮江",
yinjiang_shuying_info: "锁定技,当你在出牌阶段摸牌后,获得牌堆底的1张牌。若该牌为红色,则对所有敌方角色造成1点伤害。当你在同一个阶段内以此法造成过两次活更多的伤害后,该技能失效直到回合结束。",
zhuri_shuying: "逐日",
zhuri_shuying_info: "锁定技,你使用红色牌时摸1张牌。当你成为【杀】或普通锦囊牌的目标后,结算完毕后将该牌放置在牌堆底。",
shaohao_boss_shuying: "少昊",
baiyi_shuying: "白仪",
baiyi_shuying_info: "锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其1张牌;若当前轮数小于5,对其造成1点雷电伤害;若当前轮数小于7,其随机弃置1张牌。",
shenen_shuying: "神恩",
shenen_shuying_info: "锁定技,所有己方角色使用牌锁定技,所有己方角色使用牌没有距离限制。",
xuannv_boss_shuying: "玄女",
xuanlie_shuying: "玄烈",
xuanlie_shuying_info: "锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成1点伤害。",
jiutian_shuying: "九天",
jiutian_shuying_info: "锁定技,准备阶段,你获得所有敌方角色各1张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。",
dishi_shuying: "帝师",
dishi_shuying_info: "当你使用【杀】或普通锦囊牌指定目标时,如果目标数为1,可以增加一个目标;如果目标数大于1,你可以减少一个目标。",
hanba_boss_shuying: "旱魃",
xinji_shuying: "心悸",
xinji_shuying_info: "锁定技,当友方于回合外因弃置而失去手牌时,你对当前角色造成1点伤害。",
zhiri_shuying: "炙日",
zhiri_shuying_info: "锁定技,当敌方角色使用红色锦囊牌指定目标后,你摸1张牌。",
fenshi_shuying: "焚世",
fenshi_shuying_info: "锁定技,准备阶段,若你的手牌数小于体力值,则将手牌摸至于体力值相等;若你的手牌数大于体力值,则你对敌方角色造成共计X点伤害,点数随机分配(X为手牌数减体力值)。",
};
// 汇总所有主题翻译,并阻止同名翻译在后加载模块中被覆盖。
const translates = mergeBossModules("翻译", [
["十二生肖", shiershengxiao],
["捉鬼驱邪", zhuoguiquxie],
["青青子衿", qingqingzijin],
["天书乱斗", tianshu],
]);
export default translates;
+7 -7
View File
@@ -1,6 +1,6 @@
const bossList = {
//stage1: ["riyeyoushen_boss_shuying", "dongzhuo_boss_shuying"],
stage1: ["chenlong_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "xvgou_boss_shuying"],
// stage1: ["caocao_boss_shuying", "dongzhuo_boss_shuying"],
stage1: ["zishu_boss_shuying", "wuma_boss_shuying", "chenlong_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "xvgou_boss_shuying"],
stage2: ["heibaiwuchang_boss_shuying", "yvsai_boss_shuying", "huangfeng_boss_shuying", "riyeyoushen_boss_shuying"],
stage3: ["caocao_boss_shuying", "simayi_boss_shuying", "lvbu_boss_shuying", "dongzhuo_boss_shuying"],
stage4: ["shuishengonggong_boss_shuying", "shaohao_boss_shuying", "xuannv_boss_shuying", "hanba_boss_shuying"],
@@ -121,11 +121,11 @@ const bossDifficulty = {
...bossDifficultyTemplates.zhuoguiquxie,
hard: {
...bossDifficultyTemplates.zhuoguiquxie.hard,
skill: ["xixing_shuying"],
skills: ["xixing_shuying"],
},
nightmare: {
...bossDifficultyTemplates.zhuoguiquxie.nightmare,
skill: ["xixing_shuying", "taiping_shuying"],
skills: ["xixing_shuying", "taiping_shuying"],
},
},
yvsai_boss_shuying: {
@@ -139,7 +139,7 @@ const bossDifficulty = {
...bossDifficultyTemplates.zhuoguiquxie,
nightmare: {
...bossDifficultyTemplates.zhuoguiquxie.nightmare,
skill: ["duane_shuying"],
skills: ["duane_shuying"],
},
},
@@ -194,11 +194,11 @@ const bossDifficulty = {
...bossDifficultyTemplates.tianshuluandou,
hard: {
...bossDifficultyTemplates.tianshuluandou.hard,
skill: ["shuishen_shuying"],
skills: ["shuishen_shuying"],
},
nightmare: {
...bossDifficultyTemplates.tianshuluandou.nightmare,
skill: ["shuishen_shuying"],
skills: ["shuishen_shuying"],
},
},
shaohao_boss_shuying: { ...bossDifficultyTemplates.tianshuluandou, },
+64 -20
View File
@@ -9,9 +9,10 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const state = {
difficulty: "normal",
stageIndex: 0,
currentBosses: [],
selectedSkills: {},
selectedSkillSet: {},
difficultyBossMap: {},
originalBossMap: {},
playerSideIds: {},
roundStartPlayer: null,
roundStartSeat: 0,
@@ -59,8 +60,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const getVirtualIdolIds = () => tianshuConfig.virtualIdolList || Object.values(tianshuConfig.virtualIdols).flat();
// 判断指定角色是否为虚拟偶像。
const isVirtualIdol = player => Boolean(player && (getVirtualIdolIds().includes(player.name1) || getVirtualIdolIds().includes(player.name)));
// 判断指定角色是否属于当前关卡 Boss 列表。
const isCurrentStageBoss = player => Boolean(player && (state.currentBosses.includes(player.name1) || state.currentBosses.includes(player.name)));
// 判断指定角色是否为当前关卡实际召唤的 Boss;玩家变身为同名动态武将时不会被误判。
const isCurrentStageBoss = player => Boolean(player?.storage?.shuYing_tianshu_stageBoss && player.storage.shuYing_tianshu_stageIndex == state.stageIndex);
// 判断指定角色是否属于开局捕获的玩家方。
const isCapturedPlayerSide = player => Boolean(getPlayerKey(player) && state.playerSideIds[getPlayerKey(player)]);
// 获取玩家方角色列表,可按是否存活、是否包含虚拟偶像过滤。
@@ -227,8 +228,9 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
state.difficulty = keys[Math.max(0, controls.indexOf(result.control))] || "normal";
_status[tianshuConfig.settings.difficultyStatusKey] = state.difficulty;
};
// 在指定座位创建关卡 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。
const addStageBoss = (position, name) => {
// 在指定座位创建当前难度的动态 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。
const addStageBoss = (position, originalName) => {
const name = state.difficultyBossMap[originalName] || originalName;
const boss = game.addFellow(position, name, "zoominanim");
boss.dataset.position = position;
boss.side = true;
@@ -236,6 +238,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
boss.setIdentity(boss.identity);
boss.storage.shuYing_tianshu_stageBoss = true;
boss.storage.shuYing_tianshu_stageIndex = state.stageIndex;
boss.storage.shuYing_tianshu_originalBossName = originalName;
game.addVideo("setIdentity", boss, boss.identity);
if (game.playerMap) game.playerMap[boss.dataset.position] = boss;
game.arrangePlayers();
@@ -246,7 +249,12 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const num = Number(value);
return Number.isFinite(num) ? num : null;
};
const getBossConfigName = player => player?.name1 || player?.name || "";
// 将动态难度武将 ID 还原为 config.js 中使用的原始 Boss ID。
const getBossConfigName = source => {
if (typeof source == "string") return state.originalBossMap[source] || source;
const name = source?.storage?.shuYing_tianshu_originalBossName || source?.name1 || source?.name || "";
return state.originalBossMap[name] || name;
};
// 读取当前 Boss 在当前难度下的数值配置;单 Boss 配置覆盖默认难度配置。
const getBossDifficultyConfig = player => {
const defaultConfig = tianshuConfig.bossDifficulty?.default?.[state.difficulty] || {};
@@ -256,27 +264,61 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
// 补充技能只读取当前难度配置,不跨难度继承低难度技能。
const getBossDifficultySkills = player => {
const defaultSkills = tianshuConfig.bossDifficulty?.default?.[state.difficulty]?.skills || [];
const bossSkills = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty]?.skills || [];
const bossConfig = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty] || {};
const bossSkills = [];
if (Array.isArray(bossConfig.skills)) bossSkills.addArray(bossConfig.skills);
const skills = [];
for (const skill of defaultSkills.concat(bossSkills)) {
if (skill && !skills.includes(skill)) skills.push(skill);
}
return skills;
};
// Boss 难度数值属于出场面板修正
const applyBossDifficultyConfig = player => {
const config = getBossDifficultyConfig(player);
// 根据原始武将和当前难度,生成包含准确体力、上限及追加技能的隐藏动态武将。
const createDifficultyBossCharacter = originalName => {
const original = lib.character[originalName];
if (!original) return null;
const source = get.convertedCharacter(original);
const data = {};
for (const [key, value] of Object.entries(source)) {
data[key] = value && typeof value == "object" ? get.copy(value) : value;
}
const config = getBossDifficultyConfig(originalName);
const maxHp = getConfigNumber(config.maxHp);
const maxHpBonus = getConfigNumber(config.maxHpBonus) || 0;
const hp = getConfigNumber(config.hp);
const hpBonus = getConfigNumber(config.hpBonus) || 0;
player.maxHp = Math.max(1, maxHp ?? player.maxHp + maxHpBonus);
player.hp = Math.min(Math.max(0, hp ?? player.hp + hpBonus), player.maxHp);
for (const skill of getBossDifficultySkills(player)) {
if (!player.hasSkill(skill)) player.addSkill(skill);
data.maxHp = Math.max(1, maxHp ?? source.maxHp + maxHpBonus);
data.hp = Math.min(Math.max(1, hp ?? source.hp + hpBonus), data.maxHp);
data.skills = source.skills.slice();
data.skills.addArray(getBossDifficultySkills(originalName));
data.isHiddenBoss = true;
data.isBossAllowed = true;
data.isAiForbidden = true;
data.tempname = Array.isArray(source.tempname) ? source.tempname.slice() : [];
data.tempname.add(originalName);
return get.convertedCharacter(data);
};
// 难度选择后一次性注册本局全部动态 Boss,供召唤、变身及武将牌技能读取共同使用。
const prepareDifficultyBossCharacters = () => {
state.difficultyBossMap = {};
state.originalBossMap = {};
const names = tianshuConfig.stages.flatMap(stage => stage?.bosses || []);
for (const originalName of Array.from(new Set(names))) {
const character = createDifficultyBossCharacter(originalName);
if (!character) continue;
const dynamicName = `${originalName}_tianshu_${state.difficulty}`;
state.difficultyBossMap[originalName] = dynamicName;
state.originalBossMap[dynamicName] = originalName;
lib.character[dynamicName] = character;
lib.translate[dynamicName] = lib.translate[originalName] || get.translation(originalName);
if (lib.translate[`${originalName}_ab`]) lib.translate[`${dynamicName}_ab`] = lib.translate[`${originalName}_ab`];
if (lib.translate[`${originalName}_prefix`]) lib.translate[`${dynamicName}_prefix`] = lib.translate[`${originalName}_prefix`];
if (lib.characterIntro?.[originalName]) lib.characterIntro[dynamicName] = lib.characterIntro[originalName];
if (lib.characterTitle?.[originalName]) lib.characterTitle[dynamicName] = lib.characterTitle[originalName];
lib.config.forbidai?.add(dynamicName);
lib.hiddenCharacters?.add(dynamicName);
lib.skilllist.addArray(character.skills);
}
player.update();
return config;
};
// 让开局占位主 Boss 静默死亡,避免作为关卡 Boss 参与后续流程。
const killInitialBossSilently = async () => {
@@ -445,7 +487,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
return result;
};
};
// 召唤指定关卡的 Boss,并完成初始 Boss 静默退场和难度强化。
// 召唤指定关卡的动态 Boss,并按配置补足仅属于登场流程的初始手牌。
const spawnStage = async (stageIndex = state.stageIndex) => {
state.stageIndex = stageIndex;
state.clearingStage = false;
@@ -456,7 +498,6 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
const boss = pool.randomRemove();
if (boss) bosses.push(boss);
}
state.currentBosses = bosses.slice();
const seats = getBossSeats();
if (stageIndex == 0) await killInitialBossSilently();
bosses.forEach((boss, index) => {
@@ -464,7 +505,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
});
game.arrangePlayers();
for (const player of game.players.filter(current => isCurrentStageBoss(current))) {
const config = applyBossDifficultyConfig(player);
const config = getBossDifficultyConfig(player);
const startCards = getConfigNumber(config.startCards);
if (startCards === null) continue;
const needCards = startCards - player.countCards("h");
@@ -618,7 +659,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
if (!isCurrentStageBoss(deadBoss)) return;
if (deadBoss.storage.shuYing_tianshu_bossDieHandled) return;
deadBoss.storage.shuYing_tianshu_bossDieHandled = true;
const stageCleared = !state.currentBosses.some(name => game.players.some(current => current.name1 == name || current.name == name));
const stageCleared = !game.players.some(current => isCurrentStageBoss(current));
const finalCleared = stageCleared && state.stageIndex >= tianshuConfig.stages.length - 1;
if (!finalCleared && event.source && event.source.isIn()) {
if (event.source.hp < event.source.maxHp) await event.source.recover(tianshuConfig.reward.killRecover);
@@ -637,6 +678,7 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
capturePlayerSide();
state.pendingSpawn = true;
await chooseDifficulty(player);
prepareDifficultyBossCharacters();
_status.additionalReward = () => 500;
["shandian", "huoshan", "hongshui", "fulei", "lebu", "bingliang"].forEach(name => lib.inpile.remove(name));
Array.from(ui.cardPile.childNodes).forEach(node => {
@@ -662,6 +704,8 @@ export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) {
},
handleBossDie,
isCurrentStageBoss,
getBossConfigName,
getDifficultyBossName: originalName => state.difficultyBossMap[originalName] || originalName,
// 判断角色是否为玩家方成员,默认要求角色仍在场。
isPlayerSideMember: (player, aliveOnly = true) => Boolean(player && (!aliveOnly || player.isIn()) && isCapturedPlayerSide(player) && !isVirtualIdol(player)),
// 判断玩家方是否已经全灭。