You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/script/battle/view/MapDragGuideHelper.ts

583 lines
24 KiB

3 weeks ago
import {
BattleBagCardKind,
3 weeks ago
canDragMapTile,
canDropWeapon2OnCell,
isBagToolTextType,
2 weeks ago
isElementGlyphType,
15 hours ago
isShovelTextType,
3 weeks ago
MapCellKind,
PlaceRejectReason,
rejectReasonToMessage,
Weapon2TileType,
TileSource,
} from 'db://assets/mx/module/merge';
import type { BattleBagSession } from '../bag/BattleBagSession';
import { WeaponComposeConfigProvider } from '../bag/WeaponComposeConfigProvider';
import type { MapGrid } from '../map/legacy/MapGrid';
import type { MapManager } from '../map/legacy/MapManager';
/** 地图格在拖拽悬停中的落子语义 */
export type MapDragDropState = 'valid' | 'blocked' | 'none';
/** 拖拽跟随字块上的落子提示(textGrid select / redSelect) */
export type DragPlacementHint = 'valid' | 'blocked' | 'off';
/** 拖字来源快照(升级判定用) */
export interface DragTileSourceInfo {
/** Weapon2.id */
weapon2Id: number;
/** 背包合成后的 Weapon1(单字) */
weapon1Id?: number;
/** 字块等级 */
level?: number;
/** 字块来源(用于 functiontype=3 的未开挖格规则) */
source?: TileSource;
/** 地图拖起原格;同组合内互拖时排除升级提示 */
fromMapGrid?: number;
/** 局内产卡种类(倍率卡/属性卡) */
battleCardKind?: number;
3 weeks ago
}
/** 拖拽期间高亮可落子格(加号仅 valid;type3 blocked 可走格仅红底) */
export class MapDragGuideHelper {
/**
* @param source 拖字来源快照;须带 source= PassiveFunction3 时未开挖格才有引导
*/
static show(
map: MapManager,
session: BattleBagSession,
weapon2Id: number,
source?: Omit<DragTileSourceInfo, 'weapon2Id'>,
): void {
WeaponComposeConfigProvider.instance.load();
const grids = map.mapGrids;
if (!Array.isArray(grids)) return;
const w2Type = Number(
WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id),
) as Weapon2TileType;
const dragSource: DragTileSourceInfo = { weapon2Id, ...source };
for (const mg of grids) {
if (!mg?.node?.isValid || !mg.node.active) continue;
mg.showWalkSprite = false;
mg.showBlockedWalkSprite = false;
mg.isGuideStatus = false;
mg.isBlockedGuideStatus = false;
const state = MapDragGuideHelper.getMapDragDropState(session, w2Type, mg, dragSource);
MapDragGuideHelper._applyMapDragGuideVisual(mg, w2Type, state);
}
}
static liveCellKind(mg: MapGrid): MapCellKind {
if (mg.isCanWalk) return MapCellKind.Walkable;
// 可放置优先:dig+putText 双标按 Put
if (mg.isCanPutText) return MapCellKind.Put;
if (mg.isCanBeDig) return MapCellKind.Dig;
return MapCellKind.Put;
}
static hasSpiderWeb(mg: MapGrid): boolean {
if (mg.hasSpiderWeb) return true;
const silk = mg.node?.getChildByName('spiderSilk');
return !!silk?.isValid && silk.active;
}
/**
15 hours ago
* 引导加号语义:五行字空 Put 为 valid;铲拖到空 Dig 为 valid。
3 weeks ago
* 已有字块的格不显示 +(升级目标由 isBagDropLineTarget / hover 单独放行)。
*/
static getMapDragDropState(
session: BattleBagSession,
w2Type: Weapon2TileType,
mg: MapGrid,
source?: DragTileSourceInfo,
): MapDragDropState {
if (!mg?.node?.isValid) return 'none';
const gridIndex = mg.Index;
const hasTile = session.merge.map.hasTile(gridIndex);
const canPutGrid = !!mg.isCanPutGrid;
if (MapDragGuideHelper.hasSpiderWeb(mg)) return 'blocked';
if (source?.weapon2Id != null
&& WeaponComposeConfigProvider.instance.passiveSpawnKeepsDigCell(source.weapon2Id)) {
if (!session.validator.isAllowedCampForPlace(gridIndex)) return 'blocked';
if (hasTile) return 'none';
15 hours ago
if (mg.isCanBeDig && !mg.isCanWalk) return 'valid';
3 weeks ago
if (mg.isCanPutText && !mg.isCanBeDig && !mg.isCanWalk) return 'valid';
if (mg.isCanWalk) return 'blocked';
return 'none';
}
if (source?.source === TileSource.PassiveFunction3) {
15 hours ago
if (mg.isCanBeDig && !mg.isCanWalk) {
3 weeks ago
if (!session.validator.isAllowedCampForPlace(gridIndex)) return 'blocked';
return hasTile ? 'none' : 'valid';
}
return hasTile ? 'none' : 'blocked';
}
15 hours ago
if (isShovelTextType(Number(w2Type))) {
// 已可放置的双标不当挖格
if (mg.isCanBeDig && !mg.isCanPutText && !mg.isCanWalk && !hasTile) {
if (!session.validator.isAllowedCampForPlace(gridIndex)) return 'blocked';
return 'valid';
}
if (mg.isCanWalk || mg.isCanPutText) return 'blocked';
return 'none';
}
2 weeks ago
if (isElementGlyphType(w2Type)) {
3 weeks ago
// 可放置优先:双标 put+dig 允许落字
if (mg.isCanPutText && !mg.isCanWalk) {
if (!session.validator.isAllowedCampForPlace(gridIndex)) return 'blocked';
if (hasTile) return 'none';
return 'valid';
}
if (mg.isCanWalk || mg.isCanBeDig) return 'blocked';
return 'none';
}
if (w2Type === Weapon2TileType.Grid) {
if (!mg.isCanWalk || hasTile) return 'none';
if (!canPutGrid) return 'blocked';
if (!session.validator.isAllowedCampForPlace(gridIndex)) return 'blocked';
if (session.validator.canPlaceType3Grid(gridIndex)) return 'valid';
return 'blocked';
}
return 'none';
}
static getMapHoverHint(
session: BattleBagSession,
w2Type: Weapon2TileType,
mg: MapGrid,
source?: DragTileSourceInfo,
): DragPlacementHint {
const state = MapDragGuideHelper.getMapDragDropState(session, w2Type, mg, source);
if (state === 'valid') return 'valid';
if (state === 'blocked') return 'blocked';
if (source && MapDragGuideHelper._canBattleCardDropOnGrid(session, mg.Index, source)) {
return 'valid';
}
if (source && MapDragGuideHelper._canBagToolDropOnGrid(session, mg.Index, source)) {
return 'valid';
}
3 weeks ago
if (source && MapDragGuideHelper._canKeepDigMerge(session, mg.Index, source)) {
return 'valid';
}
if (source && MapDragGuideHelper._canUpgradeDropOnGrid(session, mg.Index, source)) {
return 'valid';
}
if (source && MapDragGuideHelper._canKeepDigSwap(session, mg.Index, source)) {
return 'valid';
}
if (source && MapDragGuideHelper._canSwapDropOnGrid(session, mg.Index, source)) {
return 'valid';
}
return 'off';
}
static getMapDropFailMessage(
session: BattleBagSession,
w2Type: Weapon2TileType,
mg: MapGrid,
source?: DragTileSourceInfo,
): string {
if (MapDragGuideHelper.getMapDragDropState(session, w2Type, mg, source) === 'valid') {
return '';
}
if (source && MapDragGuideHelper._canBattleCardDropOnGrid(session, mg.Index, source)) {
return '';
}
if (source && MapDragGuideHelper._canBagToolDropOnGrid(session, mg.Index, source)) {
return '';
}
3 weeks ago
if (source && MapDragGuideHelper._canKeepDigMerge(session, mg.Index, source)) {
return '';
}
if (source && MapDragGuideHelper._canUpgradeDropOnGrid(session, mg.Index, source)) {
return '';
}
if (source && MapDragGuideHelper._canKeepDigSwap(session, mg.Index, source)) {
return '';
}
if (source && MapDragGuideHelper._canSwapDropOnGrid(session, mg.Index, source)) {
return '';
}
1 day ago
if (source) {
const toolMsg = MapDragGuideHelper._bagToolDropFailMessage(session, mg.Index, source);
if (toolMsg) return toolMsg;
}
3 weeks ago
return MapDragGuideHelper.getBlockedDropMessage(session, w2Type, mg);
}
static getBlockedDropMessage(
session: BattleBagSession,
w2Type: Weapon2TileType,
mg: MapGrid,
): string {
if (!session.validator.isAllowedCampForPlace(mg.Index)) {
return rejectReasonToMessage(PlaceRejectReason.WrongCampArea);
}
if (MapDragGuideHelper.hasSpiderWeb(mg) && w2Type === Weapon2TileType.Grid) {
return rejectReasonToMessage(PlaceRejectReason.SpiderWebBlocked);
}
if (w2Type === Weapon2TileType.Grid) {
const cellKind = MapDragGuideHelper.liveCellKind(mg);
const hasTile = session.merge.map.hasTile(mg.Index);
const canPutGrid = !!mg.isCanPutGrid;
if (
cellKind === MapCellKind.Walkable
&& !hasTile
&& canPutGrid
&& !session.validator.canPlaceType3Grid(mg.Index)
) {
return rejectReasonToMessage(PlaceRejectReason.PathBlocked);
}
}
return rejectReasonToMessage(PlaceRejectReason.WrongCellKind);
}
/**
* 拉线松手可落点:空可放置格,或已占格上的升级 / 交换。
*/
static isBagDropLineTarget(
map: MapManager,
session: BattleBagSession,
source: DragTileSourceInfo,
gridIndex: number,
): boolean {
const mg = map.getMapGridByIndex(gridIndex);
if (!mg?.node?.isValid || !mg.node.active) return false;
WeaponComposeConfigProvider.instance.load();
const w2Type = Number(
WeaponComposeConfigProvider.instance.getWeapon2Type(source.weapon2Id),
) as Weapon2TileType;
if (MapDragGuideHelper.getMapDragDropState(session, w2Type, mg, source) === 'valid') {
return true;
}
if (MapDragGuideHelper._canBattleCardDropOnGrid(session, gridIndex, source)) {
return true;
}
if (MapDragGuideHelper._canBagToolDropOnGrid(session, gridIndex, source)) {
return true;
}
3 weeks ago
if (MapDragGuideHelper._canKeepDigMerge(session, gridIndex, source)) {
return true;
}
if (MapDragGuideHelper._canUpgradeDropOnGrid(session, gridIndex, source)) {
return true;
}
if (MapDragGuideHelper._canKeepDigSwap(session, gridIndex, source)) {
return true;
}
return MapDragGuideHelper._canSwapDropOnGrid(session, gridIndex, source);
}
static isType3SpiderWebBlockedDrop(
map: MapManager,
session: BattleBagSession,
weapon2Id: number,
gridIndex: number,
): boolean {
const mg = map.getMapGridByIndex(gridIndex);
if (!mg?.node?.isValid || !mg.node.active) return false;
WeaponComposeConfigProvider.instance.load();
const w2Type = Number(
WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id),
) as Weapon2TileType;
2 weeks ago
if (isElementGlyphType(w2Type) || w2Type !== Weapon2TileType.Grid) return false;
3 weeks ago
if (WeaponComposeConfigProvider.instance.passiveSpawnKeepsDigCell(weapon2Id)) return false;
if (!MapDragGuideHelper.hasSpiderWeb(mg)) return false;
return MapDragGuideHelper.getMapDragDropState(session, w2Type, mg, { weapon2Id }) === 'blocked';
}
static isType3PathBlockedDrop(
map: MapManager,
session: BattleBagSession,
weapon2Id: number,
gridIndex: number,
): boolean {
const mg = map.getMapGridByIndex(gridIndex);
if (!mg?.node?.isValid || !mg.node.active) return false;
WeaponComposeConfigProvider.instance.load();
const w2Type = Number(
WeaponComposeConfigProvider.instance.getWeapon2Type(weapon2Id),
) as Weapon2TileType;
2 weeks ago
if (isElementGlyphType(w2Type) || w2Type !== Weapon2TileType.Grid) return false;
3 weeks ago
if (MapDragGuideHelper.hasSpiderWeb(mg)) return false;
const cellKind = MapDragGuideHelper.liveCellKind(mg);
const hasTile = session.merge.map.hasTile(gridIndex);
const canPutGrid = !!mg.isCanPutGrid;
if (!canDropWeapon2OnCell(w2Type, cellKind, hasTile, canPutGrid)) return false;
return !session.validator.canPlaceType3Grid(gridIndex);
}
static clear(map: MapManager | null): void {
if (!map) return;
const grids = map.mapGrids;
if (!Array.isArray(grids)) return;
for (const mg of grids) {
if (!mg?.node?.isValid) continue;
mg.isGuideStatus = false;
mg.isBlockedGuideStatus = false;
mg.showWalkSprite = false;
mg.showBlockedWalkSprite = false;
}
}
/** 来源是否为 functiontype=3 产字 */
private static _isFunction3Source(source?: DragTileSourceInfo): boolean {
return source?.source === TileSource.PassiveFunction3;
}
/** 未开挖格上的 keepDig 文字可与同来源/同 id 文字合成 */
private static _canKeepDigMerge(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
const target = session.merge.map.getTile(gridIndex);
if (!target || target.weapon2Id !== source.weapon2Id) return false;
if ((target.level ?? 1) !== (source.level ?? 1)) return false;
WeaponComposeConfigProvider.instance.load();
const provider = WeaponComposeConfigProvider.instance;
const sourceFunction3 = MapDragGuideHelper._isFunction3Source(source);
const targetFunction3 = target.source === TileSource.PassiveFunction3;
if (sourceFunction3 || targetFunction3) {
if (!(sourceFunction3 && targetFunction3)) return false;
const weapon1Id = (source.weapon1Id && source.weapon1Id > 0)
? source.weapon1Id
: provider.getWeapon1ByGroupSequence([source.weapon2Id]);
if (weapon1Id == null) return false;
return (source.level ?? 1) < provider.getMaxWeaponLevel(weapon1Id);
}
if (!provider.passiveSpawnKeepsDigCell(source.weapon2Id)) return false;
if (!provider.passiveSpawnKeepsDigCell(target.weapon2Id)) return false;
return (source.level ?? 1) < provider.getKeepDigTileMaxLevel(source.weapon2Id);
}
/** 两棵保持 Dig 的蟠桃树不能合成时允许交换 */
private static _canKeepDigSwap(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
WeaponComposeConfigProvider.instance.load();
const provider = WeaponComposeConfigProvider.instance;
if (!provider.passiveSpawnKeepsDigCell(source.weapon2Id)) return false;
const target = session.merge.map.getTile(gridIndex);
if (!target) return false;
if (!provider.passiveSpawnKeepsDigCell(target.weapon2Id)) return false;
if (!session.validator.isAllowedCampForPlace(gridIndex)) return false;
return true;
}
/**
* 倍率卡/属性卡可落到己方阵营已占的五行字上(含已激活上阵)。
* @param session 背包会话
* @param gridIndex 地图格
* @param source 拖拽来源
*/
private static _canBattleCardDropOnGrid(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
const kind = Math.floor(Number(source.battleCardKind) || 0);
if (kind !== BattleBagCardKind.Multiplier && kind !== BattleBagCardKind.Attr) return false;
const tile = session.merge.map.getTile(gridIndex);
if (!tile || tile.battleCardKind) return false;
if (!session.validator.isAllowedCampForPlace(gridIndex)) return false;
WeaponComposeConfigProvider.instance.load();
const dstType = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(tile.weapon2Id));
return isElementGlyphType(dstType);
}
/**
1 day ago
* Text 功能符可落到合法阵营已占五行字上(衰弱符仅对方,读对端 merge)。
* @param session 背包会话
* @param gridIndex 地图格
* @param source 拖拽来源
*/
private static _canBagToolDropOnGrid(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
if (Math.floor(Number(source.battleCardKind) || 0) > 0) return false;
WeaponComposeConfigProvider.instance.load();
const toolType = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(source.weapon2Id));
if (!isBagToolTextType(toolType)) return false;
const ownCamp = session.validator.isAllowedCampForPlace(gridIndex);
if (toolType === 13) {
if (ownCamp) return false;
} else if (!ownCamp) {
return false;
}
1 day ago
const mapOwner = toolType === 13 ? session.resolveOpponentBagSession() : session;
if (!mapOwner) return false;
const tile = mapOwner.merge.map.getTile(gridIndex);
if (!tile || tile.battleCardKind) return false;
const dstType = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(tile.weapon2Id));
if (!isElementGlyphType(dstType)) return false;
const lv = Math.max(1, Math.floor(Number(tile.level) || 1));
if (toolType === 13) return lv > 1;
if (toolType === 10 || toolType === 11) {
return lv < WeaponComposeConfigProvider.instance.getElementMaxLevel(dstType);
}
return true;
}
1 day ago
/**
* 功能符落点失败文案;非功能符或不适用返回空串。
* @param session 背包会话
* @param gridIndex 地图格
* @param source 拖拽来源
*/
private static _bagToolDropFailMessage(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): string {
if (Math.floor(Number(source.battleCardKind) || 0) > 0) return '';
WeaponComposeConfigProvider.instance.load();
const toolType = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(source.weapon2Id));
if (!isBagToolTextType(toolType)) return '';
const ownCamp = session.validator.isAllowedCampForPlace(gridIndex);
if (toolType === 13) {
if (ownCamp) return rejectReasonToMessage(PlaceRejectReason.InvalidMerge);
} else if (!ownCamp) {
return rejectReasonToMessage(PlaceRejectReason.WrongCampArea);
}
const mapOwner = toolType === 13 ? session.resolveOpponentBagSession() : session;
if (!mapOwner) return rejectReasonToMessage(PlaceRejectReason.InvalidMerge);
const tile = mapOwner.merge.map.getTile(gridIndex);
if (!tile || tile.battleCardKind) return rejectReasonToMessage(PlaceRejectReason.WrongCellKind);
const dstType = Math.floor(WeaponComposeConfigProvider.instance.getWeapon2Type(tile.weapon2Id));
if (!isElementGlyphType(dstType)) return rejectReasonToMessage(PlaceRejectReason.InvalidMerge);
const lv = Math.max(1, Math.floor(Number(tile.level) || 1));
if (toolType === 13 && lv <= 1) return '数字已是最小值,无法使用';
if ((toolType === 10 || toolType === 11)
&& lv >= WeaponComposeConfigProvider.instance.getElementMaxLevel(dstType)) {
return '数字已达上限,无法使用!';
}
return rejectReasonToMessage(PlaceRejectReason.InvalidMerge);
}
3 weeks ago
private static _canUpgradeDropOnGrid(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
WeaponComposeConfigProvider.instance.load();
const provider = WeaponComposeConfigProvider.instance;
const w2Type = Number(provider.getWeapon2Type(source.weapon2Id)) as Weapon2TileType;
2 weeks ago
if (!isElementGlyphType(w2Type)) return false;
3 weeks ago
const tile = session.merge.map.getTile(gridIndex);
if (!tile) return false;
2 weeks ago
if (!session.validator.isAllowedCampForPlace(gridIndex)) return false;
if (source.fromMapGrid != null && source.fromMapGrid === gridIndex) return false;
const srcLevel = Math.floor(source.level ?? 1);
const dstLevel = Math.floor(tile.level ?? 1);
const dstType = Math.floor(provider.getWeapon2Type(tile.weapon2Id));
if (dstType !== w2Type || srcLevel !== dstLevel) return false;
return srcLevel < provider.getElementMaxLevel(w2Type);
3 weeks ago
}
/**
* 已占可放置格:双方均为 type1/2 且目标可拖起时交换(与 MergeSession 一致)。
*/
private static _canSwapDropOnGrid(
session: BattleBagSession,
gridIndex: number,
source: DragTileSourceInfo,
): boolean {
WeaponComposeConfigProvider.instance.load();
const provider = WeaponComposeConfigProvider.instance;
const srcType = Number(provider.getWeapon2Type(source.weapon2Id)) as Weapon2TileType;
2 weeks ago
if (!isElementGlyphType(srcType)) {
3 weeks ago
return false;
}
const tile = session.merge.map.getTile(gridIndex);
if (!tile) return false;
if (!session.validator.isAllowedCampForPlace(gridIndex)) return false;
2 weeks ago
if (session.validator.getLiveCellKind(gridIndex) !== MapCellKind.Put) return false;
3 weeks ago
const dstType = Number(provider.getWeapon2Type(tile.weapon2Id)) as Weapon2TileType;
2 weeks ago
if (!isElementGlyphType(dstType)) {
3 weeks ago
return false;
}
return canDragMapTile(tile, dstType, false);
}
private static _applyMapDragGuideVisual(
mg: MapGrid,
w2Type: Weapon2TileType,
state: MapDragDropState,
): void {
if (state === 'none') return;
if (state === 'valid') {
if (w2Type === Weapon2TileType.Grid) {
mg.showWalkSprite = true;
}
mg.isGuideStatus = true;
return;
}
if (w2Type === Weapon2TileType.Grid) {
mg.showBlockedWalkSprite = true;
}
}
/**
* 己方阵营背包中是否存在任一字块可落到地图(空 Put / 升级 / 交换,与拉线落子一致)。
* @param campAreaId CampAreaID(本机 0→1、敌方 1→2)
*/
static hasLocalCampBagPlacement(session: BattleBagSession, map: MapManager, campAreaId: number): boolean {
WeaponComposeConfigProvider.instance.load();
const slots = session.getBagSlots();
const grids = map.mapGrids;
if (!Array.isArray(grids)) return false;
for (let si = 0; si < slots.length; si++) {
const slot = slots[si];
if (!slot) continue;
const source: DragTileSourceInfo = {
weapon2Id: slot.weapon2Id,
weapon1Id: slot.weapon1Id,
level: slot.level,
};
for (let gi = 0; gi < grids.length; gi++) {
const mg = grids[gi];
if (!mg?.node?.isValid || !mg.node.active) continue;
if ((mg.CampAreaID | 0) !== campAreaId) continue;
if (MapDragGuideHelper.isBagDropLineTarget(map, session, source, mg.Index)) {
return true;
}
}
}
return false;
}
/**
2 weeks ago
* 己方阵营仍可将五行字放入的空 Put 格数量(与拖拽 + 号一致)。
3 weeks ago
* @param campAreaId CampAreaID(本机 0→1、敌方 1→2)
*/
static countLocalEmptyPutGrids(session: BattleBagSession, map: MapManager, campAreaId: number): number {
WeaponComposeConfigProvider.instance.load();
2 weeks ago
// 五行落空 Put 规则与具体 type 无关,取 1 作探测类型
const w2Type = 1 as Weapon2TileType;
3 weeks ago
let n = 0;
const grids = map.mapGrids;
if (!Array.isArray(grids)) return 0;
for (const mg of grids) {
if (!mg?.node?.isValid || !mg.node.active) continue;
if ((mg.CampAreaID | 0) !== campAreaId) continue;
if (MapDragGuideHelper.getMapDragDropState(session, w2Type, mg) === 'valid') {
n++;
}
}
return n;
}
/** 己方阵营是否仍有可放置空 Put */
static hasLocalEmptyPutGrid(session: BattleBagSession, map: MapManager, campAreaId: number): boolean {
return MapDragGuideHelper.countLocalEmptyPutGrids(session, map, campAreaId) > 0;
}
}