Compare commits

...

8 Commits

19 changed files with 626 additions and 676 deletions

View File

@ -31,6 +31,7 @@ import {
TextAlign
} from './textboxTyper';
import { SetupComponentOptions } from '@motajs/system-ui';
import { texture } from '../elements';
//#region TextContent
@ -173,6 +174,8 @@ export const TextContent = defineComponent<
const renderContent = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx;
ctx.textBaseline = 'top';
ctx.lineWidth = props.strokeWidth ?? 2;
ctx.lineJoin = 'round';
for (const data of renderable) {
if (data.cut) break;
switch (data.type) {
@ -181,14 +184,15 @@ export const TextContent = defineComponent<
ctx.fillStyle = data.fillStyle;
ctx.strokeStyle = data.strokeStyle;
ctx.font = data.font;
const text = data.text.slice(0, data.pointer);
if (props.fill ?? true) {
ctx.fillText(text, data.x, data.y);
}
if (props.stroke) {
ctx.strokeText(text, data.x, data.y);
}
if (props.fill ?? true) {
ctx.fillText(text, data.x, data.y);
}
break;
}
case TextContentType.Icon: {
@ -248,6 +252,8 @@ export interface TextboxProps extends TextContentProps, DefaultProps {
titleStroke?: CanvasStyle;
/** 标题文字与边框间的距离默认为4 */
titlePadding?: number;
/** 图标 */
icon?: AllIdsWithNone;
/** 最大宽度 */
width: number;
}
@ -307,30 +313,10 @@ export const Textbox = defineComponent<
keyof TextboxEmits,
TextboxSlots
>((props, { slots, expose, emit }) => {
const contentData = shallowReactive<TextContentProps>({ width: 200 });
const data = shallowReactive<TextboxProps>({ width: 200 });
const setContentData = () => {
contentData.breakChars = props.breakChars ?? '';
contentData.font = props.font ?? new Font();
contentData.ignoreLineEnd = props.ignoreLineEnd ?? '';
contentData.ignoreLineStart = props.ignoreLineStart ?? '';
contentData.interval = props.interval ?? 0;
contentData.keepLast = props.keepLast ?? false;
contentData.lineHeight = props.lineHeight ?? 0;
contentData.text = props.text ?? '';
contentData.textAlign = props.textAlign ?? TextAlign.Left;
contentData.wordBreak = props.wordBreak ?? WordBreak.Space;
contentData.fill = props.fill ?? true;
contentData.stroke = props.stroke ?? false;
contentData.fillStyle = props.fillStyle ?? '#fff';
contentData.strokeStyle = props.strokeStyle ?? '#000';
contentData.strokeWidth = props.strokeWidth ?? 2;
contentData.loc = props.loc;
contentData.width = props.width;
};
const setTextboxData = () => {
// Textbox
data.backColor = props.backColor ?? '#222';
data.winskin = props.winskin;
data.padding = props.padding ?? 8;
@ -341,9 +327,28 @@ export const Textbox = defineComponent<
data.width = props.width ?? props.loc?.[2] ?? 200;
data.height = props.height ?? props.loc?.[3] ?? 200;
data.title = props.title ?? '';
data.icon = props.icon;
// TextContent
data.breakChars = props.breakChars ?? '';
data.font = props.font ?? new Font();
data.ignoreLineEnd = props.ignoreLineEnd ?? '';
data.ignoreLineStart = props.ignoreLineStart ?? '';
data.interval = props.interval ?? 0;
data.keepLast = props.keepLast ?? false;
data.lineHeight = props.lineHeight ?? 0;
data.text = props.text ?? '';
data.textAlign = props.textAlign ?? TextAlign.Left;
data.wordBreak = props.wordBreak ?? WordBreak.Space;
data.fill = props.fill ?? true;
data.stroke = props.stroke ?? false;
data.fillStyle = props.fillStyle ?? '#fff';
data.strokeStyle = props.strokeStyle ?? '#000';
data.strokeWidth = props.strokeWidth ?? 2;
data.loc = props.loc;
data.width = props.width;
};
setContentData();
setTextboxData();
watch(props, () => {
@ -361,23 +366,53 @@ export const Textbox = defineComponent<
const tw = ref(data.titlePadding! * 2);
/** 标题高度 */
const th = ref(data.titlePadding! * 2);
const contentX = computed(() => {
if (hasIcon.value) {
return data.padding! + 40;
} else {
return data.padding!;
}
});
const contentY = computed(() => {
const height = th.value;
return data.title ? height : 0;
});
const backHeight = computed(() => data.height! - contentY.value);
const contentWidth = computed(() => data.width! - data.padding! * 2);
const contentWidth = computed(() => {
if (hasIcon.value) {
return data.width! - data.padding! * 2 - 40;
} else {
return data.width! - data.padding! * 2;
}
});
const contentHeight = computed(
() => data.height! - data.padding! * 2 - contentY.value
);
const iconLoc = computed<ElementLocator>(() => {
const y = contentY.value;
const pad = data.padding!;
const icon = data.icon;
if (isNil(icon) || icon === 'none') {
return [];
} else {
const num = texture.idNumberMap[icon];
const renderable = texture.getRenderable(num);
if (!renderable) return [];
const [, , w, h] = renderable.render[0];
return [pad, pad + y, w, h];
}
});
const hasIcon = computed(() => {
return !isNil(data.icon) && data.icon !== 'none';
});
const onSetText = () => {
nextTick(() => {
titleElement.value?.requestBeforeFrame(() => {
if (titleElement.value) {
const { width, height } = titleElement.value;
tw.value = width + data.padding! * 2;
th.value = height + data.padding! * 2;
tw.value = width + data.titlePadding! * 2;
th.value = height + data.titlePadding! * 2;
}
});
});
@ -385,6 +420,8 @@ export const Textbox = defineComponent<
//#region store
let lastTitle = data.title;
/** 结束打字机 */
const storeEmits: TextboxStoreEmits = {
endType() {
@ -397,23 +434,23 @@ export const Textbox = defineComponent<
hidden.value = false;
},
update(value) {
if (data.title !== value.title) {
data.title = value.title;
if (value.title !== lastTitle) {
onSetText();
lastTitle = value.title;
}
},
setText(text) {
if (contentData.text === text) {
if (data.text === text) {
content.value?.retype();
} else {
contentData.text = text;
data.text = text;
}
}
};
const store = TextboxStore.use(
props.id ?? getNextTextboxId(),
contentData,
data,
storeEmits
);
@ -447,7 +484,7 @@ export const Textbox = defineComponent<
id={props.id}
hidden={hidden.value}
alpha={data.alpha}
loc={props.loc}
loc={data.loc}
>
{data.title && (
<container zIndex={10} loc={[0, 0, tw.value, th.value]}>
@ -468,6 +505,7 @@ export const Textbox = defineComponent<
fillStyle={data.titleFill}
strokeStyle={data.titleStroke}
font={data.titleFont}
strokeWidth={2}
></text>
</container>
)}
@ -485,10 +523,23 @@ export const Textbox = defineComponent<
fillStyle={data.backColor}
></g-rect>
)}
{hasIcon.value && (
<icon icon={data.icon as AllIds} loc={iconLoc.value} animate />
)}
{hasIcon.value && (
<g-rect
loc={iconLoc.value}
strokeStyle="gold"
fillStyle="#222"
lineWidth={2}
fill
stroke
/>
)}
<TextContent
{...contentData}
{...data}
ref={content}
x={data.padding!}
x={contentX.value}
y={contentY.value + data.padding!}
width={contentWidth.value}
height={contentHeight.value}
@ -531,7 +582,7 @@ export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
typing: boolean = false;
private constructor(
private readonly data: TextboxProps,
public readonly data: TextboxProps,
private readonly emits: TextboxStoreEmits
) {
super();

View File

@ -336,6 +336,12 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
this.config[key] = value;
}
}
if (config.font) {
this.config.fontFamily = config.font.family;
this.config.fontSize = config.font.size;
this.config.fontItalic = config.font.italic;
this.config.fontWeight = config.font.weight;
}
this.parser.setStatus({
fillStyle: this.config.fillStyle,
fontFamily: this.config.fontFamily,
@ -528,7 +534,8 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
return;
}
this.emit('typeStart');
this.lastTypeTime = Date.now();
// 减去间隔是为了第一个字可以立刻打出来,不然看起来有延迟
this.lastTypeTime = Date.now() - this.config.interval - 1;
this.typing = true;
}
@ -628,11 +635,11 @@ export class TextContentParser {
* @param st
*/
setStatus(st: Partial<ParserStatus>) {
if (!isNil(st.fillStyle)) this.status.fillStyle = st.fillStyle;
if (!isNil(st.fontSize)) this.status.fontSize = st.fontSize;
if (!isNil(st.fontFamily)) this.status.fontFamily = st.fontFamily;
if (!isNil(st.fontItalic)) this.status.fontItalic = st.fontItalic;
if (!isNil(st.fontWeight)) this.status.fontWeight = st.fontWeight;
if (!isNil(st.fillStyle)) this.initStatus.fillStyle = st.fillStyle;
if (!isNil(st.fontSize)) this.initStatus.fontSize = st.fontSize;
if (!isNil(st.fontFamily)) this.initStatus.fontFamily = st.fontFamily;
if (!isNil(st.fontItalic)) this.initStatus.fontItalic = st.fontItalic;
if (!isNil(st.fontWeight)) this.initStatus.fontWeight = st.fontWeight;
}
/**
@ -856,6 +863,7 @@ export class TextContentParser {
this.font = this.buildFont();
this.resolved = '';
this.wordBreak = [0];
this.wordBreakRule = this.config.wordBreak;
this.nodePointer = 0;
this.blockPointer = 0;
this.nowNode = 0;
@ -975,7 +983,6 @@ export class TextContentParser {
}
this.addTextNode(text.length, false);
return this.splitLines(width);
}
@ -1014,7 +1021,7 @@ export class TextContentParser {
this.blocks.forEach(v => {
if (v.type !== TextContentType.Wait) {
width += v.width;
height = v.height;
if (v.height > height) height = v.height;
}
});
const line: ITextContentLine = {
@ -1198,7 +1205,11 @@ export class TextContentParser {
this.newLine();
const nextStart = this.wordBreak[index];
const nextEnd = this.wordBreak[end];
this.bsStart = index;
if (index === this.bsStart) {
this.bsStart = this.bsStart + 1;
} else {
this.bsStart = index;
}
this.bsEnd = end;
const metrics = this.measure(node, nextStart, nextEnd);
if (metrics.width < width) {
@ -1338,11 +1349,9 @@ export class TextContentParser {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.type === TextContentType.Text) {
this.wordBreak = [node.text.length];
this.wordBreak = [0, node.text.length];
}
const pointer =
node.type === TextContentType.Text ? node.text.length : 1;
const block = this.generateBlock(node, pointer);
const block = this.generateBlock(node, 1);
this.pushBlock(block, 1);
}
this.newLine();

View File

@ -66,8 +66,8 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
*
* @param id id
*/
setIcon(id: AllIds | AllNumbers) {
if (id === 0) {
setIcon(id: AllIdsWithNone | AllNumbers) {
if (id === 0 || id === 'none') {
this.renderable = void 0;
return;
}

View File

@ -1,6 +1,6 @@
import { createApp, Font } from '@motajs/render';
import { defineComponent } from 'vue';
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
import { DEFAULT_FONT, MAIN_HEIGHT, MAIN_WIDTH } from './shared';
import { hook, loading } from '@user/data-base';
import { createLoopMap } from './loopMap';
import { createElements } from './elements';
@ -45,7 +45,7 @@ export function createRender() {
sceneController.open(GameTitleUI, {});
});
Font.setDefaults(new Font('normal', 18));
Font.setDefaults(DEFAULT_FONT);
}
export * from './components';

View File

@ -1,4 +1,5 @@
import { ElementLocator } from '@motajs/render-core';
import { Font } from '@motajs/render-style';
// 本文件为 UI 配置文件,你可以修改下面的每个常量来控制 UI 的显示参数,每个常量都有注释说明
@ -60,15 +61,17 @@ export const CENTER_LOC: ElementLocator = [
/** 弹框的宽度,使用在内置 UI 与组件中,包括确认框、选择框、等待框等 */
export const POP_BOX_WIDTH = MAP_WIDTH / 2;
/** 默认字体 */
export const DEFAULT_FONT = new Font('normal', 18);
//#region 存档界面
/** 存档缩略图尺寸 */
export const SAVE_ITEM_SIZE = 150;
export const SAVE_ITEM_SIZE = MAP_BLOCK_WIDTH * 10;
/** 单个存档上方显示第几号存档的高度 */
export const SAVE_ITEM_TOP = 24;
/** 单个存档下方显示这个存档信息的高度 */
export const SAVE_ITEM_DOWN = 16;
export const SAVE_ITEM_DOWN = 24;
/** 单个存档高度,包括存档下方的信息 */
export const SAVE_ITEM_HEIGHT = SAVE_ITEM_SIZE + SAVE_ITEM_TOP + SAVE_ITEM_DOWN;
/** 存档间距 */

View File

@ -36,8 +36,7 @@ import {
RightStatusBar
} from './statusBar';
import { ReplayingStatus } from './toolbar';
import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state';
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
import { getHeroStatusOn } from '@user/data-state';
import { hook } from '@user/data-base';
import { FloorDamageExtends, FloorItemDetail } from '../elements';
import { LayerGroupPortal } from '../legacy/portal';
@ -76,14 +75,14 @@ const MainScene = defineComponent(() => {
const mainTextboxProps: Props<typeof Textbox> = {
text: '',
hidden: true,
loc: [0, MAP_HEIGHT - 150, MAIN_WIDTH, 150],
loc: [0, MAP_HEIGHT - 150, MAP_WIDTH, 150],
zIndex: 30,
fillStyle: '#fff',
titleFill: 'gold',
font: new Font('normal'),
titleFont: new Font('normal', 20, 'px', 700),
winskin: 'winskin2.png',
interval: 100,
interval: 30,
lineHeight: 4,
width: MAP_WIDTH
};
@ -100,6 +99,13 @@ const MainScene = defineComponent(() => {
}
});
const replayStatus: ReplayingStatus = reactive({
replaying: false,
playing: false,
speed: 1,
played: 0,
total: 0
});
const leftStatus: ILeftHeroStatus = reactive({
hp: 0,
atk: 0,
@ -112,26 +118,10 @@ const MainScene = defineComponent(() => {
redKey: 0,
floor: 'MT0',
lv: '',
regen: 0,
exAtk: 0,
magicDef: 0
});
const replayStatus: ReplayingStatus = reactive({
playing: false,
speed: 1,
played: 0,
total: 0
replay: replayStatus
});
const rightStatus: IRightHeroStatus = reactive({
autoSkill: false,
skillName: '',
skillDesc: '',
jumpCount: 0,
springCount: 0,
floor: 'MT0',
replaying: false,
replayStatus,
night: 0
exampleHard: 0
});
//#region 状态更新
@ -140,7 +130,6 @@ const MainScene = defineComponent(() => {
hideStatus.value = core.getFlag('hideStatusBar', false);
const hero = core.status.hero;
const floor = core.status.floorId;
leftStatus.atk = getHeroStatusOn('atk');
leftStatus.hp = getHeroStatusOn('hp');
leftStatus.def = getHeroStatusOn('def');
@ -152,35 +141,15 @@ const MainScene = defineComponent(() => {
leftStatus.redKey = core.itemCount('redKey');
leftStatus.floor = core.status.floorId;
leftStatus.lv = core.getLvName(hero.lv);
leftStatus.regen = getHeroStatusOn('hpmax');
leftStatus.exAtk = getHeroStatusOn('mana');
leftStatus.magicDef = getHeroStatusOn('magicDef');
rightStatus.autoSkill = HeroSkill.getAutoSkill();
rightStatus.skillName = HeroSkill.getSkillName();
rightStatus.skillDesc = HeroSkill.getSkillDesc();
rightStatus.night = NightSpecial.getNight(floor);
rightStatus.floor = floor;
rightStatus.replaying = core.isReplaying();
const { pausing, speed, toReplay, totalList } = core.status.replay;
replayStatus.replaying = core.isReplaying();
replayStatus.playing = !pausing;
replayStatus.speed = speed;
replayStatus.played = totalList.length - toReplay.length;
replayStatus.total = totalList.length;
if (HeroSkill.learnedSkill(HeroSkill.Jump)) {
if (jumpIgnoreFloor.has(floor)) {
rightStatus.jumpCount = -2;
} else {
rightStatus.jumpCount = 3 - (flags[`jump_${floor}`] ?? 0);
}
} else {
rightStatus.jumpCount = -1;
}
if (core.hasFlag('spring')) {
rightStatus.springCount = 50 - (flags.springCount ?? 0);
} else {
rightStatus.springCount = -1;
}
rightStatus.exampleHard = flags.hard;
};
const updateDataFallback = () => {

View File

@ -69,8 +69,8 @@ const saveBtnProps = {
} satisfies SetupComponentOptions<SaveItemProps>;
export const SaveItem = defineComponent<SaveItemProps>(props => {
const font = new Font('normal', 18);
const statusFont = new Font('normal', 14);
const font = Font.defaults({ size: 18 });
const statusFont = Font.defaults({ size: 14 });
const w = computed(() => props.loc[2] ?? 200);
const h = computed(() => props.loc[3] ?? 200);
@ -93,6 +93,11 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
return `${hp}/${atk}/${def}`;
}
});
const timeText = computed(() => {
if (!props.data) return '';
const date = new Date(props.data.data.time);
return date.toLocaleString();
});
const strokeStyle = computed(() => {
if (props.selected) return props.inDelete ? 'red' : 'gold';
@ -115,8 +120,8 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
<text
text={name.value}
font={font}
loc={[w.value / 2, SAVE_ITEM_TOP - 4]}
anc={[0.5, 1]}
loc={[w.value / 2, 0]}
anc={[0.5, 0]}
/>
<g-rect
loc={imgLoc.value}
@ -143,7 +148,14 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
text={statusText.value}
fillStyle="yellow"
font={statusFont}
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN + 2]}
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN]}
anc={[0.5, 0]}
/>
<text
text={timeText.value}
fillStyle="yellow"
font={statusFont}
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN + 12]}
anc={[0.5, 0]}
/>
</container>

View File

@ -1,26 +1,16 @@
import { GameUI, SetupComponentOptions } from '@motajs/system-ui';
import { computed, defineComponent, ref, watch } from 'vue';
import { computed, defineComponent, ref } from 'vue';
import { TextContent } from '../components';
import {
DefaultProps,
ElementLocator,
Sprite,
Font,
MotaOffscreenCanvas2D
} from '@motajs/render';
import { transitionedColor } from '../use';
import { linear } from 'mutate-animate';
import { Scroll } from '../components';
import { getArea, MinimapDrawer } from '@motajs/legacy-ui';
import { DefaultProps, ElementLocator, Font } from '@motajs/render';
import {
NumpadToolbar,
PlayingToolbar,
ReplayingStatus,
ReplayingToolbar
} from './toolbar';
import { HeroSkill } from '@user/data-state';
import { openViewMap } from './viewmap';
import { mainUIController } from './controller';
import { MAIN_HEIGHT, STATUS_BAR_WIDTH } from '../shared';
export interface ILeftHeroStatus {
hp: number;
@ -34,33 +24,12 @@ export interface ILeftHeroStatus {
redKey: number;
floor: FloorIds;
lv: string;
/** 生命回复 */
regen: number;
/** 额外攻击 */
exAtk: number;
/** 魔法防御 */
magicDef: number;
replay: ReplayingStatus;
}
export interface IRightHeroStatus {
/** 自动切换技能 */
autoSkill: boolean;
/** 当前开启的技能 */
skillName: string;
/** 技能描述 */
skillDesc: string;
/** 跳跃剩余次数,-1 表示未开启,-2表示当前楼层不能跳 */
jumpCount: number;
/** 治愈之泉剩余次数,-1 表示未开启 */
springCount: number;
/** 当前楼层 */
floor: FloorIds;
/** 是否正在录像播放 */
replaying: boolean;
/** 录像播放状态 */
replayStatus: ReplayingStatus;
/** 极昼永夜 */
night: number;
/** 示例属性,以游戏难度作为示例 */
exampleHard: number;
}
interface StatusBarProps<T> extends DefaultProps {
@ -85,15 +54,20 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
const s = p.status;
const f = core.formatBigNumber;
const inNumpad = ref(false);
const floorName = computed(() => core.floors[s.floor]?.title ?? '');
const key = (num: number) => {
return num.toString().padStart(2, '0');
};
const onNumpad = () => {
inNumpad.value = !inNumpad.value;
};
const font1 = Font.defaults({ size: 18 });
const font2 = Font.defaults({ size: 18, weight: 700 });
const font3 = Font.defaults({ size: 14, weight: 700 });
const iconLoc = (n: number): ElementLocator => {
return [16, 76 + 44 * n, 32, 32];
@ -108,11 +82,6 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
return [width / 2, y, void 0, void 0, 0.5, 0.5];
};
const right = (y: number): ElementLocator => {
const width = p.loc[2] ?? 200;
return [width - 16, y, void 0, void 0, 1, 0.5];
};
const keyCount = 3;
const keyY = 92 + 44 * 6;
const keyLoc = (n: number): ElementLocator => {
@ -137,30 +106,10 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
<text text={s.lv} loc={central(54)} font={font1}></text>
<image image={hpIcon} loc={iconLoc(0)}></image>
<text text={f(s.hp)} loc={textLoc(0)} font={font1}></text>
<text
text={`+${f(s.regen)}/t`}
loc={right(110)}
font={font3}
fillStyle="#a7ffa7"
></text>
<image image={atkIcon} loc={iconLoc(1)}></image>
<text text={f(s.atk)} loc={textLoc(1)} font={font1}></text>
<text
text={`+${f(s.exAtk)}`}
loc={right(154)}
font={font3}
fillStyle="#ffd3d3"
></text>
<image image={defIcon} loc={iconLoc(2)}></image>
<text text={f(s.def)} loc={textLoc(2)} font={font1}></text>
{s.magicDef > 0 && (
<text
text={`+${f(s.magicDef)}`}
loc={right(198)}
font={font3}
fillStyle="#b0bdff"
></text>
)}
<image image={mdefIcon} loc={iconLoc(3)}></image>
<text text={f(s.mdef)} loc={textLoc(3)} font={font1}></text>
<image image={moneyIcon} loc={iconLoc(4)}></image>
@ -185,232 +134,59 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
font={font2}
fillStyle="#f88"
></text>
<text
text="技能树"
loc={central(396)}
font={font1}
cursor="pointer"
></text>
<text
text="查看技能"
loc={central(428)}
font={font1}
cursor="pointer"
></text>
<g-line
lineWidth={1}
strokeStyle="#888"
line={[
0,
MAIN_HEIGHT - 113,
STATUS_BAR_WIDTH,
MAIN_HEIGHT - 113
]}
/>
{inNumpad.value ? (
<NumpadToolbar
loc={[0, MAIN_HEIGHT - 113, STATUS_BAR_WIDTH, 113]}
onNumpad={onNumpad}
/>
) : s.replay.replaying ? (
<ReplayingToolbar
loc={[0, MAIN_HEIGHT - 113, STATUS_BAR_WIDTH, 113]}
status={s.replay}
/>
) : (
<PlayingToolbar
loc={[0, MAIN_HEIGHT - 113, STATUS_BAR_WIDTH, 113]}
onNumpad={onNumpad}
/>
)}
</container>
);
},
statusBarProps
);
interface RightStatusBarMisc {
name: string;
value: string;
nameColor: string;
valueColor: string;
}
export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
p => {
const font1 = new Font('normal', 18);
const font2 = new Font('normal', 16);
// p.status 就是你在 main.tsx 中传入的属性内容,用法与左侧状态栏完全一致
const minimap = ref<Sprite>();
const inNumpad = ref(false);
const onNumpad = () => {
inNumpad.value = !inNumpad.value;
};
const s = p.status;
const skill = computed(() =>
s.autoSkill ? '已开启自动切换' : s.skillName
);
const skillDesc = computed(() =>
s.autoSkill ? '自动切换技能时,会自动选择最优技能' : s.skillDesc
);
const skillColor = transitionedColor('#284', 200, linear())!;
watch(
() => s.autoSkill,
value => {
skillColor.set(value ? '#284' : '#824');
}
);
const miscData = computed<RightStatusBarMisc[]>(() => {
const data: RightStatusBarMisc[] = [];
if (s.jumpCount !== -1) {
const text =
s.jumpCount === -2 ? '不可跳跃' : s.jumpCount.toString();
data.push({
name: '跳跃剩余',
nameColor: '#fff',
value: text,
valueColor: '#fff'
});
}
if (s.springCount >= 0) {
data.push({
name: '治愈之泉',
nameColor: '#a7ffa7',
value: s.springCount.toString(),
valueColor: '#a7ffa7'
});
}
if (s.night !== 0) {
const text = s.night.toString();
data.push({
name: '极昼永夜',
nameColor: '#a3f8ff',
value: s.night > 0 ? '+' + text : text,
valueColor: s.night > 0 ? '#a7ffa7' : '#ffa7a7'
});
}
return data;
});
const central = (y: number): ElementLocator => {
const width = p.loc[2] ?? 200;
return [width / 2, y, void 0, void 0, 0.5, 0.5];
};
const middle = (x: number, y: number): ElementLocator => {
return [x, y, void 0, void 0, 0, 0.5];
};
const changeAutoSkill = () => {
const auto = !s.autoSkill;
HeroSkill.setAutoSkill(auto);
core.status.route.push(`set:autoSkill:${auto}`);
core.updateStatusBar();
};
const area = getArea();
const minimapDrawer = new MinimapDrawer(
document.createElement('canvas')
);
minimapDrawer.noBorder = true;
minimapDrawer.scale = 4;
minimapDrawer.showInfo = true;
let linked = false;
const drawMinimap = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx;
ctx.scale(
1 / core.domStyle.scale / devicePixelRatio,
1 / core.domStyle.scale / devicePixelRatio
);
if (!linked) {
minimapDrawer.link(canvas.canvas);
linked = true;
}
if (minimapDrawer.nowFloor !== s.floor) {
minimapDrawer.drawedThumbnail = {};
} else {
minimapDrawer.drawToTarget();
ctx.restore();
return;
}
minimapDrawer.nowFloor = s.floor;
minimapDrawer.nowArea =
Object.keys(area).find(v =>
area[v].includes(core.status.floorId)
) ?? '';
minimapDrawer.locateMap(minimapDrawer.nowFloor);
minimapDrawer.drawMap();
};
watch(
() => s.floor,
() => {
minimap.value?.update();
}
);
const text = `这里是右侧状态栏,如果左侧状态栏不够用可以在 \\r[gold]statusBar.tsx\\r 中编写内容,如果不需要此状态栏,可以在 \\r[gold]shared.ts\\r 中关闭此状态栏。`;
return () => {
return (
<container loc={p.loc} hidden={p.hidden}>
<g-rectr
loc={[10, 10, 160, 24]}
circle={[6]}
fillStyle={skillColor.ref.value}
onClick={changeAutoSkill}
cursor="pointer"
></g-rectr>
<text
loc={central(22)}
text={skill.value}
font={font1}
onClick={changeAutoSkill}
cursor="pointer"
/>
<TextContent
loc={[10, 42, 160, 60]}
text={skillDesc.value}
font={new Font('normal', 14)}
width={160}
lineHeight={4}
></TextContent>
<g-line
line={[0, 107, 180, 107]}
strokeStyle="#888"
lineWidth={1}
zIndex={-20}
></g-line>
<Scroll loc={[0, 107, 180, 100]}>
{miscData.value
.map((v, i) => {
return [
<text
text={v.name}
loc={middle(10, 16 + i * 22)}
fillStyle={v.nameColor}
font={font2}
></text>,
<text
text={v.value}
loc={middle(100, 16 + i * 22)}
fillStyle={v.valueColor}
font={font2}
></text>
];
})
.flat()}
</Scroll>
<g-line
line={[0, 207, 180, 207]}
strokeStyle="#888"
lineWidth={1}
zIndex={-20}
></g-line>
<sprite
ref={minimap}
loc={[10, 207, 160, 160]}
render={drawMinimap}
></sprite>
<g-line
line={[0, 367, 180, 367]}
strokeStyle="#888"
lineWidth={1}
zIndex={-20}
></g-line>
{inNumpad.value ? (
<NumpadToolbar
loc={[0, 367, 180, 113]}
onNumpad={onNumpad}
/>
) : s.replaying ? (
<ReplayingToolbar
loc={[0, 367, 180, 113]}
status={s.replayStatus}
/>
) : (
<PlayingToolbar
loc={[0, 367, 180, 113]}
onNumpad={onNumpad}
/>
)}
loc={[8, 8]}
text={text}
width={STATUS_BAR_WIDTH - 16}
autoHeight
lineHeight={8}
/>
<text loc={[8, 270]} text="示例内容" />
<text
loc={[8, 300]}
text={`游戏难度:${p.status.exampleHard}`}
/>
</container>
);
};

View File

@ -145,6 +145,8 @@ export const PlayingToolbar = defineComponent<
}, toolbarProps);
export interface ReplayingStatus {
/** 是否处在录像播放状态 */
replaying: boolean;
/** 是否正在播放 */
playing: boolean;
/** 录像播放速度 */
@ -153,6 +155,8 @@ export interface ReplayingStatus {
played: number;
/** 总长度 */
total: number;
/** 是否是录像模式 */
replaying: boolean;
}
export interface ReplayingProps extends ToolbarProps {
@ -378,3 +382,20 @@ export const NumpadToolbar = defineComponent<
</container>
);
}, toolbarProps);
export const MixedToolbar = defineComponent<ReplayingProps>(props => {
const inNumpad = ref(false);
const onNumpad = () => {
inNumpad.value = !inNumpad.value;
};
return () =>
inNumpad.value ? (
<NumpadToolbar loc={props.loc} onNumpad={onNumpad} />
) : props.status.replaying ? (
<ReplayingToolbar loc={props.loc} status={props.status} />
) : (
<PlayingToolbar loc={props.loc} onNumpad={onNumpad} />
);
}, replayingProps);

View File

@ -52,6 +52,7 @@ export class Text extends RenderItem<ETextEvent> {
ctx.strokeStyle = this.strokeStyle ?? 'transparent';
ctx.font = this.font.string();
ctx.lineWidth = this.strokeWidth;
ctx.lineJoin = 'round';
if (this.strokeStyle) {
ctx.strokeText(this.text, stroke, this.descent + stroke + SAFE_PAD);

View File

@ -810,7 +810,11 @@ action
| comment_s
| autoText_s
| scrollText_s
| createTextbox_s
| deleteTextbox_s
| setText_s
| createTip_s
| deleteTip_s
| tip_s
| setValue_s
| setEquip_s
@ -940,30 +944,27 @@ action
text_s
: '标题' EvalString? '图标' EvalString? '像素坐标 x' IntString? 'y' IntString? '宽' IntString? '高' IntString? '维持文本' Bool '打字间隔' IntString? '行高' IntString? BGNL? Newline
: '标题' EvalString? '图标' EvalString? '文本框id' EvalString? '像素坐标 x' IntString? 'y' IntString? '宽' IntString? '高' IntString? BGNL? Newline
EvalString_Multi Newline
/* text_s
tooltip : text显示一段文字剧情,选项较多请右键点击帮助
tooltip : text显示一段文字剧情文本框id 默认使用 main-textbox
helpUrl : /_docs/#/instruction
previewBlock : true
allIds : ['EvalString_1']
default : ["小妖精","fairy","","","","",false,"","","欢迎使用事件编辑器"]
default : ["","","","","","","","欢迎使用事件编辑器"]
EvalString_0= EvalString_0 ? (', "title": "'+EvalString_0+'"') : '';
EvalString_1= EvalString_1 ? (', "icon": "'+EvalString_1+'"') : '';
EvalString_2= EvalString_2 ? (', "textbox": "'+EvalString_2+'"') : '';
IntString_0= IntString_0 ? (', "x": '+IntString_0) : '';
IntString_1= IntString_1 ? (', "y": '+IntString_1) : '';
IntString_2= IntString_2 ? (', "width": '+IntString_2) : '';
IntString_3= IntString_3 ? (', "height": '+IntString_3) : '';
IntString_4= IntString_4 ? (', "interval": '+IntString_4) : '';
IntString_5= IntString_5 ? (', "lineHeight": '+IntString_5) : '';
var code = '{"type": "text"'+EvalString_0+EvalString_1+IntString_0+IntString_1+IntString_2+IntString_3+',"keepLast":'+Bool_0+IntString_4+IntString_5+'"text":"'+EvalString_Multi_0+'"},\n';
var code = '{"type": "text"'+EvalString_0+EvalString_1+EvalString_2+IntString_0+IntString_1+IntString_2+IntString_3+', "text": "'+EvalString_Multi_0+'"},\n';
return code
*/;
comment_s
: '添加注释' ':' EvalString_Multi Newline
@ -979,7 +980,7 @@ return code;
*/;
autoText_s
: '自动剧情文本: 标题' EvalString? '图像' EvalString? '对话框效果' EvalString? '时间' Int BGNL? EvalString_Multi Newline
: '自动剧情文本: 标题' EvalString? '图标' EvalString? '文本框id' EvalString? '像素坐标 x' IntString? 'y' IntString? '宽' IntString? '高' IntString? '时间' Int BGNL? EvalString_Multi Newline
/* autoText_s
@ -987,40 +988,54 @@ tooltip : autoText自动剧情文本,用户无法跳过自动剧情文本,大
helpUrl : /_docs/#/instruction
doubleclicktext : EvalString_Multi_0
allIds : ['EvalString_1']
default : ["小妖精","fairy","",3000,"用户无法跳过自动剧情文本,大段剧情文本请添加“是否跳过剧情”的提示"]
var title='';
if (EvalString_0==''){
if (EvalString_1=='' )title='';
else title='\\t['+EvalString_1+']';
} else {
if (EvalString_1=='')title='\\t['+EvalString_0+']';
else title='\\t['+EvalString_0+','+EvalString_1+']';
}
if(EvalString_2 && !(/^(up|center|down|hero|this)(,(hero|null|\d+,\d+|\d+))?$/.test(EvalString_2))) {
throw new Error('对话框效果的用法请右键点击帮助');
}
EvalString_2 = EvalString_2 && ('\\b['+EvalString_2+']');
var code = '{"type": "autoText", "text": "'+title+EvalString_2+EvalString_Multi_0+'", "time": '+Int_0+'},\n';
default : ["","","","","","","",3000,"用户无法跳过自动剧情文本,大段剧情文本请添加“是否跳过剧情”的提示"]
EvalString_0= EvalString_0 ? (', "title": "'+EvalString_0+'"') : '';
EvalString_1= EvalString_1 ? (', "icon": "'+EvalString_1+'"') : '';
EvalString_2= EvalString_2 ? (', "textbox": "'+EvalString_2+'"') : '';
IntString_0= IntString_0 ? (', "x": '+IntString_0) : '';
IntString_1= IntString_1 ? (', "y": '+IntString_1) : '';
IntString_2= IntString_2 ? (', "width": '+IntString_2) : '';
IntString_3= IntString_3 ? (', "height": '+IntString_3) : '';
Int_0= ', "time": '+Int_0;
var code = '{"type": "autoText"'+EvalString_0+EvalString_1+EvalString_2+IntString_0+IntString_1+IntString_2+IntString_3+Int0+', "text": "'+EvalString_Multi_0+'},\n';
return code;
*/;
scrollText_s
: '滚动剧情文本:' '时间' Int '行距' Number '不等待执行完毕' Bool? BGNL? EvalString_Multi Newline
: '滚动剧情文本事件将在 2.B.1 实装'
/* scrollText_s
tooltip : scrollText滚动剧情文本,将从下到上进行滚动显示。
tooltip : scrollText滚动剧情文本事件将在 2.B.1 实装
helpUrl : /_docs/#/instruction
doubleclicktext : EvalString_Multi_0
default : [5000,1.4,false,"时间是总时间可以使用setText事件来控制字体、颜色、大小、偏移量等"]
Bool_0 = Bool_0?', "async": true':'';
var code = '{"type": "scrollText", "text": "'+EvalString_Multi_0+'"'+Bool_0+', "time" :'+Int_0+', "lineHeight": '+Number_0+'},\n';
return code;
default : []
return '{"type": "scrollText"}';
*/;
createTextbox_s
: '创建文本框将在 2.B.1 实装'
/* createTextbox_s
tooltip : createTextbox创建文本框事件将在 2.B.1 实装
helpUrl : /_docs/#/instruction
default : []
return '{"type": "createTextbox"},\n'
*/;
deleteTextbox_s
: '删除文本框将在 2.B.1 实装'
/* deleteTextbox_s
tooltip : deleteTextbox删除文本框事件将在 2.B.1 实装
helpUrl : /_docs/#/instruction
default : []
return '{"type": "deleteTextbox"},\n'
*/;
setText_s
: '设置剧情文本的属性' '位置像素x' IntString? 'y' IntString? '宽' IntString? '高' IntString? '字体类型' EvalString? '字体大小' IntString? '字体线宽' IntString? BGNL?
'是否斜体' Bool? '维持文本' Bool? '打字间隔' IntString? '行高' IntString? '文字颜色' ColorString? Colour '文字描边颜色' ColorString? Colour '描边线宽' IntString? '是否填充' Bool '是否描边' Bool BGNL?
: '配置文本框' '文本框id'EvalString '位置像素x' IntString? 'y' IntString? '宽' IntString? '高' IntString? '字体类型' EvalString? '字体大小' IntString? '字体线宽' IntString? '是否斜体' Bool? BGNL?
'维持文本' Bool? '打字间隔' IntString? '行高' IntString? '文字颜色' ColorString? Colour '文字描边颜色' ColorString? Colour '描边线宽' IntString? '是否填充' Bool '是否描边' Bool BGNL?
'背景色' ColorString? Colour '背景winskin' EvalString? '文字与边框距离' IntString? '标题是否填充' Bool '标题是否描边' Bool'标题与边框的距离' IntString? BGNL?
'对齐方式' TextAlign_List '分词原则' WordBreak_List '行首禁则' EvalString? '行尾禁则' EvalString? '分词规则识别字符' EvalString? Newline
@ -1029,42 +1044,141 @@ setText_s
tooltip : setText设置文本的属性,颜色为RGB三元组或RGBA四元组,打字间隔为剧情文字添加的时间间隔,为整数或不填,字符间距为字符之间的距离,为整数或不填。
helpUrl : /_docs/#/instruction
previewBlock : true
default : ["","","","","","","",false,false,"","","",'rgba(255,255,255,1)',"",'rgba(255,255,255,1)',"",true,false,"",'rgba(255,255,255,1)',"","",true,false,"",'null','space',"","",""]
default : ["main-textbox","","","","","","","",false,false,"","","",'rgba(255,255,255,1)',"",'rgba(0,0,0,1)',"",true,false,"",'rgba(0,0,0,0.9)',"","",true,false,"",'null','null',"","",""]
Bool_0 = Bool_0 ? (', "fontItalic": '+Bool_0) : '';
Bool_1 = Bool_1 ? (', "keepLast": '+Bool_1) : '';
Bool_2 = !Bool_2 ? (', "fill": '+Bool_2) : '';
Bool_3 = Bool_3 ? (', "stroke": '+Bool_3) : '';
Bool_4 = !Bool_4 ? (', "titleFill": '+Bool_4) : '';
Bool_5 = Bool_5 ? (', "titleStroke": '+Bool_5) : '';
IntString_0= IntString_0 ? (', "x": '+IntString_0) : '';
IntString_1 = IntString_1 ? (', "y": '+IntString_1) : '';
IntString_2 = IntString_2 ? (', "width": '+IntString_2) : '';
IntString_3 = IntString_3 ? (', "height": '+IntString_3) : '';
EvalString_0 = EvalString_0 ? (', "fontFamily": '+EvalString_0) : '';
IntString_4 = IntString_4 ? (', "fontSize": '+IntString_4) : '';
IntString_5 = IntString_5 ? (', " interval": '+IntString_5) : '';
IntString_6 = IntString_6 ? (', "lineHeight": ' + IntString_6) : '';
IntString_7 = IntString_7? (', "strokeWidth": ' + IntString_7) : '';
EvalString_1 = EvalString_1 ? (', "winskin": '+EvalString_1) : '';
IntString_8 = IntString_8? (', "padding": ' + IntString_8) : '';
IntString_9 = IntString_9? (', "titlePadding": ' + IntString_9) : '';
IntString_5 = IntString_5? (', "fontWeight": ' + IntString_5) : '';
IntString_6 = IntString_6 ? (', "interval": '+IntString_6) : '';
IntString_7 = IntString_7 ? (', "lineHeight": ' + IntString_7) : '';
IntString_8 = IntString_8? (', "strokeWidth": ' + IntString_8) : '';
IntString_9 = IntString_9? (', "padding": ' + IntString_9) : '';
IntString_10 = IntString_10? (', "titlePadding": ' + IntString_10) : '';
EvalString_0 = EvalString_0 ? (', "textbox": "'+EvalString_0+'"') : '';
EvalString_1 = EvalString_1 ? (', "fontFamily": "'+EvalString_1+'"') : '';
EvalString_2 = EvalString_2 ? (', "winskin": "'+EvalString_2+'"') : '';
EvalString_3 = EvalString_3? (', "ignoreLineStart": "'+EvalString_3+'"') : '';
EvalString_4 = EvalString_4 ? (', "ignoreLineEnd": "'+EvalString_4+'"') : '';
EvalString_5 = EvalString_5 ? (', "breakChars": "'+EvalString_5+'"') : '';
ColorString_0=ColorString_0?(', "fillStyle": "rgba('+ColorString_0+')"'):'';
ColorString_1=ColorString_1?(', "strokeStyle": "rgba('+ColorString_1+')"'):'';
ColorString_2=ColorString_2?(', "backColor": "rgba('+ColorString_2+')"'):'';
TextAlign_List_0 = TextAlign_List_0==='null'?'':', "textAlign": "'+TextAlign_List_0+'"';
WordBreak_List_0 = WordBreak_List_0==='null'?'':', "wordBreak": "'+WordBreak_List_0+'"';
EvalString_2 = EvalString_2? (', "ignoreLineStart": '+EvalString_2) : '';
EvalString_3 = EvalString_3 ? (', "ignoreLineEnd": '+EvalString_3) : '';
EvalString_4 = EvalString_4 ? (', "breakChars": '+EvalString_4) : '';
ColorString_0=ColorString_0?(', "fillStyle": ['+ColorString_0+']'):'';
ColorString_1=ColorString_1?(', "strokeStyle": ['+ColorString_1+']'):'';
ColorString_2=ColorString_2?(', "backColor": ['+ColorString_2+']'):'';
var code = '{"type": "setText"'+IntString_0+IntString_1+IntString_2+IntString_3+EvalString_0+IntString_4+', " fontItalic": '+Bool_0+', " keepLast": '+Bool_1+IntString_5+IntString_6+ColorString_0+ColorString_1+IntString_7 +',"fill":'+Bool_2+',"stroke":'+Bool_3+ColorString_2+EvalString_1+IntString_8+',"titleFill":'+Bool_4+',"titleStroke":'+Bool_5+IntString_9 +TextAlign_List_0+WordBreak_List_0+EvalString_2+EvalString_3+EvalString_4+'},\n';
var code =
'{"type": "setText"'+
EvalString_0+ IntString_0+ IntString_1+ IntString_2+ IntString_3+ EvalString_1+ IntString_4+ IntString_5+ Bool_0+
Bool_1+ IntString_6+ IntString_7+ ColorString_0+ ColorString_1+ IntString_8+ Bool_2+ Bool_3+
ColorString_2+ EvalString_2+ IntString_9+ Bool_4+ Bool_5+ IntString_10+
TextAlign_List_0+ WordBreak_List_0+ EvalString_3+ EvalString_4+ EvalString_5+
'},\n';
return code;
*/;
createTip_s
: '创建提示栏事件将在 2.B.1 实装'
/* createTip_s
tooltip : createTip创建提示栏事件将在 2.B.1 实装
helpUrl : /_docs/#/instruction
default : []
return '{"type": "createTip"}';
*/;
deleteTip_s
: '删除提示栏事件将在 2.B.1 实装'
/* deleteTip_s
tooltip : createTip删除提示栏事件将在 2.B.1 实装
helpUrl : /_docs/#/instruction
default : []
return '{"type": "deleteTip"}';
*/;
tip_s
: '显示提示' ':' EvalString '图标ID' IdString? Newline
: '显示提示' '提示栏id' EvalString? '图标ID' IdString? BGNL?
'提示内容' EvalString Newline
/* tip_s
tooltip : tip显示一段提示文字
helpUrl : /_docs/#/instruction
allIds : ['IdString_0']
default : ["这段话将在左上角以气泡形式显示",""]
IdString_0 = IdString_0 && (', "icon": "' + IdString_0 + '"');
var code = '{"type": "tip", "text": "'+EvalString_0+'"'+IdString_0+'},\n';
default : ["","","这段话将在左上角以气泡形式显示"]
EvalString_0 = EvalString_0 ? (', "tip": "' + EvalString_0 + '"') : '';
IdString_0 = IdString_0 ? (', "icon": "' + IdString_0 + '"') : '';
EvalString_1 = EvalString_1 ? (', "text": "' + EvalString_1 + '"') : '';
var code = '{"type": "tip"'+EvalString_0+IdString_0+EvalString_1+'},\n';
return code;
*/;
confirm_s
: '显示确认框' ':' EvalString_Multi '超时毫秒数' Int BGNL? '确定的场合' ':' '(默认选中' Bool '' BGNL? Newline action+ '取消的场合' ':' BGNL? Newline action+ BEND Newline
/* confirm_s
tooltip : 弹出确认框
helpUrl : /_docs/#/instruction
default : ["确认要xxx吗?",0,false]
previewBlock : true
Bool_0 = Bool_0?', "default": true':''
Int_0 = Int_0 ? (', "timeout": '+Int_0) : '';
var code = ['{"type": "confirm"'+Int_0+Bool_0+', "text": "',EvalString_Multi_0,'",',
block.isCollapsed()?' "_collapsed": true,':'',
block.isEnabled()?'':' "_disabled": true,',
'\n"yes": [\n',action_0,'],\n',
'"no": [\n',action_1,']\n',
'},\n'].join('');
return code;
*/;
choices_s
: '选项' ':' EvalString_Multi? BGNL? '标题' EvalString? '图像' IdString? '超时毫秒数' Int BGNL? Newline choicesContext+ BEND Newline
/* choices_s
tooltip : choices: 给用户提供选项
helpUrl : /_docs/#/instruction
previewBlock : true
default : ["","","",0]
allIds : ['IdString_0']
EvalString_0 = EvalString_0 ? (', "title": "' + EvalString_0 + '"') : '';
IdString_0 = IdString_0 ? (', "icon": "' + IdString_0 + '"') : '';
EvalString_Multi_0 = EvalString_Multi_0 ?(', "text": "'+EvalString_Multi_0+'"'):'';
Int_0 = Int_0 ? (', "timeout": '+Int_0) : '';
var code = ['{"type": "choices"',EvalString_0,IdString_0,EvalString_Multi_0,Int_0,
block.isCollapsed()?', "_collapsed": true':'',
block.isEnabled()?'':', "_disabled": true',
', "choices": [\n',
choicesContext_0,
']},\n'].join('');
return code;
*/;
choicesContext
: '子选项' EvalString '图标' IdString? '颜色' ColorString? Colour '启用条件' EvalString? '出现条件' EvalString? BGNL? Newline action+
/* choicesContext
tooltip : 选项的选择
helpUrl : /_docs/#/instruction
default : ["提示文字:红钥匙","","","",""]
allIds : ['IdString_0']
colour : this.subColor
ColorString_0 = ColorString_0 ? (', "color": ['+ColorString_0+']') : '';
EvalString_1 = EvalString_1 && (', "need": "'+EvalString_1+'"');
EvalString_2 = EvalString_2 && (', "condition": "'+EvalString_2+'"');
IdString_0 = IdString_0?(', "icon": "'+IdString_0+'"'):'';
var collapsed=block.isCollapsed()?', "_collapsed": true':'';
var disabled=block.isEnabled()?'':', "_disabled": true';
var code = '{"text": "'+EvalString_0+'"'+IdString_0+ColorString_0+EvalString_1+EvalString_2+collapsed+disabled+', "action": [\n'+action_0+']},\n';
return code;
*/;
@ -2470,76 +2584,6 @@ var code = '{"case": "'+expression_0+'"'+Bool_0+collapsed+disabled+', "action":
return code;
*/;
choices_s
: '选项' ':' EvalString_Multi? BGNL? '标题' EvalString? '图像' IdString? '超时毫秒数' Int '宽度' IntString? BGNL? Newline choicesContext+ BEND Newline
/* choices_s
tooltip : choices: 给用户提供选项
helpUrl : /_docs/#/instruction
previewBlock : true
default : ["","流浪者","trader",0,'']
allIds : ['IdString_0']
var title='';
if (EvalString_0==''){
if (IdString_0=='')title='';
else title='\\t['+IdString_0+']';
} else {
if (IdString_0=='')title='\\t['+EvalString_0+']';
else title='\\t['+EvalString_0+','+IdString_0+']';
}
EvalString_Multi_0 = title+EvalString_Multi_0;
EvalString_Multi_0 = EvalString_Multi_0 ?(', "text": "'+EvalString_Multi_0+'"'):'';
Int_0 = Int_0 ? (', "timeout": '+Int_0) : '';
IntString_0 = IntString_0 ? (', "width": ' + IntString_0) : '';
var code = ['{"type": "choices"',EvalString_Multi_0,Int_0,IntString_0,
block.isCollapsed()?', "_collapsed": true':'',
block.isEnabled()?'':', "_disabled": true',
', "choices": [\n',
choicesContext_0,
']},\n'].join('');
return code;
*/;
choicesContext
: '子选项' EvalString '图标' IdString? '颜色' ColorString? Colour '启用条件' EvalString? '出现条件' EvalString? BGNL? Newline action+
/* choicesContext
tooltip : 选项的选择
helpUrl : /_docs/#/instruction
default : ["提示文字:红钥匙","","","",""]
allIds : ['IdString_0']
colour : this.subColor
ColorString_0 = ColorString_0 ? (', "color": ['+ColorString_0+']') : '';
EvalString_1 = EvalString_1 && (', "need": "'+EvalString_1+'"');
EvalString_2 = EvalString_2 && (', "condition": "'+EvalString_2+'"');
IdString_0 = IdString_0?(', "icon": "'+IdString_0+'"'):'';
var collapsed=block.isCollapsed()?', "_collapsed": true':'';
var disabled=block.isEnabled()?'':', "_disabled": true';
var code = '{"text": "'+EvalString_0+'"'+IdString_0+ColorString_0+EvalString_1+EvalString_2+collapsed+disabled+', "action": [\n'+action_0+']},\n';
return code;
*/;
confirm_s
: '显示确认框' ':' EvalString_Multi '超时毫秒数' Int BGNL? '确定的场合' ':' '(默认选中' Bool '' BGNL? Newline action+ '取消的场合' ':' BGNL? Newline action+ BEND Newline
/* confirm_s
tooltip : 弹出确认框
helpUrl : /_docs/#/instruction
default : ["确认要xxx吗?",0,false]
previewBlock : true
Bool_0 = Bool_0?', "default": true':''
Int_0 = Int_0 ? (', "timeout": '+Int_0) : '';
var code = ['{"type": "confirm"'+Int_0+Bool_0+', "text": "',EvalString_Multi_0,'",',
block.isCollapsed()?' "_collapsed": true,':'',
block.isEnabled()?'':' "_disabled": true,',
'\n"yes": [\n',action_0,'],\n',
'"no": [\n',action_1,']\n',
'},\n'].join('');
return code;
*/;
for_s
: '循环遍历' ': ' expression '从' EvalString '到' EvalString '步增' EvalString BGNL? Newline action+ BEND Newline

View File

@ -257,33 +257,60 @@ ActionParser.prototype.parseAction = function() {
return;
case "text": // 文字/对话
this.next = MotaActionBlocks['text_s'].xmlText([
data.title, data.icon, data.x, data.y, data.width, data.height, data.keepLast,data.interval,data.lineHeight, this.next]);
data.title,data.icon,data.textbox,data.x,data.y,data.width,data.height,data.text,this.next]);
break;
case "autoText": // 自动剧情文本
var info = this.getTitleAndPosition(data.text);
this.next = MotaActionBlocks['autoText_s'].xmlText([
info[0],info[1],info[2],data.time,info[3],this.next]);
data.title,data.icon,data.textbox,data.x,data.y,data.width,data.height,data.time,data.text,this.next]);
break;
case "scrollText":
this.next = MotaActionBlocks['scrollText_s'].xmlText([
data.time, data.lineHeight||1.4, data.async||false, this.EvalString_Multi(data.text), this.next]);
this.next = MotaActionBlocks['scrollText_s'].xmlText([this.next]);
break;
case "comment": // 注释
this.next = MotaActionBlocks['comment_s'].xmlText([this.EvalString_Multi(data.text),this.next]);
break;
case "createTextbox":
this.next = MotaActionBlocks['createTextbox_s'].xmlText([this.next]);
case "createTextbox":
this.next = MotaActionBlocks['deleteTextbox_s'].xmlText([this.next]);
case "setText": // 设置剧情文本的属性
//data.backColor=this.Colour(data.backColor);
//data.fillStyle=this.Colour(data.fillStyle);
//data.strokeStyle=this.Colour(data.strokeStyle);
const parsedFillStyle = data.fillStyle ? data.fillStyle.slice(5, -1) : '';
const parsedStrokeStyle = data.strokeStyle ? data.strokeStyle.slice(5, -1) : '';
const parsedBackColor = data.backColor ? data.backColor.slice(5, -1) : '';
this.next = MotaActionBlocks['setText_s'].xmlText([
data.x,data.y,data.width,data.height,data.fontFamily,data.fontSize,data.fontWeight,
data.fontItalic,data.keepLast,data.interval,data.lineHeight,,data.fillStyle,'rgba('+data.fillStyle+')',
data.strokeStyle,'rgba('+data.strokeStyle+')',data.strokeWidth,data.fill,data.stroke,data.backColor,'rgba('+data.backColor+')',
data.winskin,data.padding,data.titleFill,data.titleStroke,data.titlePadding,data.textAlign,data.wordBreak,data.ignoreLineStart,data.ignoreLineEnd,data.breakChars,this.next]);
data.textbox, data.x, data.y, data.width, data.height, data.fontFamily, data.fontSize, data.fontWeight, data.fontItalic,
data.keepLast, data.interval, data.lineHeight, parsedFillStyle, data.fillStyle, parsedStrokeStyle, data.strokeStyle, data.strokeWidth, data.fill, data.stroke,
parsedBackColor, data.backColor, data.winskin, data.padding, data.titleFill, data.titleStroke, data.titlePadding,
data.textAlign, data.wordBreak, data.ignoreLineStart, data.ignoreLineEnd, data.breakChars, this.next
]);
break;
case "createTip":
this.next = MotaActionBlocks['createTip_s'].xmlText([this.next]);
case "deleteTip":
this.next = MotaActionBlocks['deletetip_s'].xmlText([this.next]);
case "tip":
this.next = MotaActionBlocks['tip_s'].xmlText([
data.text,data.icon||"",this.next]);
data.tip,data.icon||"",data.text,this.next]);
break;
case "confirm": // 显示确认框
this.next = MotaActionFunctions.xmlText('confirm_s', [
this.EvalString_Multi(data.text), data.timeout||0, data["default"],
this.insertActionList(data["yes"]),
this.insertActionList(data["no"]),
this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
break;
case "choices": // 提供选项
var text_choices = null;
for(var ii=data.choices.length-1,choice;choice=data.choices[ii];ii--) {
choice.color = this.Colour(choice.color);
text_choices=MotaActionFunctions.xmlText('choicesContext', [
choice.text,choice.icon,choice.color,'rgba('+choice.color+')',choice.need||'',choice.condition||'',this.insertActionList(choice.action),text_choices],
/* isShadow */false, /*comment*/ null, /*collapsed*/ choice._collapsed, /*disabled*/ choice._disabled);
}
if (!this.isset(data.text)) data.text = '';
var info = this.getTitleAndPosition(data.text);
this.next = MotaActionFunctions.xmlText('choices_s', [
info[3],info[0],info[1],data.timeout||0,text_choices,this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
break;
case "show": // 显示
data.loc=data.loc||[];
@ -722,13 +749,6 @@ ActionParser.prototype.parseAction = function() {
this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
}
break;
case "confirm": // 显示确认框
this.next = MotaActionFunctions.xmlText('confirm_s', [
this.EvalString_Multi(data.text), data.timeout||0, data["default"],
this.insertActionList(data["yes"]),
this.insertActionList(data["no"]),
this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
break;
case "switch": // 多重条件分歧
var case_caseList = null;
for(var ii=data.caseList.length-1,caseNow;caseNow=data.caseList[ii];ii--) {
@ -740,19 +760,6 @@ ActionParser.prototype.parseAction = function() {
this.expandEvalBlock([data.condition]),
case_caseList,this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
break;
case "choices": // 提供选项
var text_choices = null;
for(var ii=data.choices.length-1,choice;choice=data.choices[ii];ii--) {
choice.color = this.Colour(choice.color);
text_choices=MotaActionFunctions.xmlText('choicesContext', [
choice.text,choice.icon,choice.color,'rgba('+choice.color+')',choice.need||'',choice.condition||'',this.insertActionList(choice.action),text_choices],
/* isShadow */false, /*comment*/ null, /*collapsed*/ choice._collapsed, /*disabled*/ choice._disabled);
}
if (!this.isset(data.text)) data.text = '';
var info = this.getTitleAndPosition(data.text);
this.next = MotaActionFunctions.xmlText('choices_s', [
info[3],info[0],info[1],data.timeout||0,data.width,text_choices,this.next], /* isShadow */false, /*comment*/ null, /*collapsed*/ data._collapsed, /*disabled*/ data._disabled);
break;
case "for": // 循环遍历
this.next = MotaActionFunctions.xmlText('for_s',[
this.expandEvalBlock([data.name]),

View File

@ -19,8 +19,8 @@ editor_blocklyconfig=(function(){
'入口方块':[
'<label text="入口方块会根据当前类型在此数组中筛选,具体控制在editor_blockly.entranceCategoryCallback中"></label>',
MotaActionFunctions.actionParser.parse([
"欢迎使用事件编辑器",
"本事件触发一次后会消失",
{"type": "text", "text": "欢迎使用事件编辑器"},
{"type": "text", "text": "本事件触发一次后会消失"},
{"type": "hide", "time": 500},
],'event'),
MotaActionFunctions.actionParser.parse({
@ -96,16 +96,19 @@ editor_blocklyconfig=(function(){
], 'splitImages'),
],
'显示文字':[
MotaActionBlocks['text_s'].xmlText(),
MotaActionBlocks['comment_s'].xmlText(),
MotaActionBlocks['text_s'].xmlText(),
MotaActionBlocks['autoText_s'].xmlText(),
MotaActionBlocks['scrollText_s'].xmlText(),
MotaActionBlocks['createTextbox_s'].xmlText(),
MotaActionBlocks['deleteTextbox_s'].xmlText(),
MotaActionBlocks['setText_s'].xmlText(),
MotaActionBlocks['createTip_s'].xmlText(),
MotaActionBlocks['deleteTip_s'].xmlText(),
MotaActionBlocks['tip_s'].xmlText(),
MotaActionBlocks['confirm_s'].xmlText(),
MotaActionBlocks['choices_s'].xmlText([
'选择剑或者盾','流浪者','man',0,'',MotaActionBlocks['choicesContext'].xmlText([
'选择剑或者盾','流浪者','man','',MotaActionBlocks['choicesContext'].xmlText([
'剑','','',null,'','',MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [3,3]}]),
])
]),

View File

@ -1100,11 +1100,8 @@ actions.prototype._clickAction_text = function () {
if (core.status.event.animateUI) return;
const Store = Mota.require('@user/client-modules').TextboxStore;
const store = Store.get('main-textbox');
// var data = core.clone(core.status.event.data.current);
// if (typeof data == 'string') data = { type: 'text', text: data };
const id = core.events.nowTextbox ?? 'main-textbox';
const store = Store.get(id);
// 打字机效果显示全部文字
if (store.typing) {
store.endType();
@ -1112,19 +1109,8 @@ actions.prototype._clickAction_text = function () {
} else {
store.hide();
}
// if (core.status.event.interval != null) {
// data.showAll = true;
// core.insertAction(data);
// core.doAction();
// return;
// }
if (!data.code) {
core.ui._animateUI('hide', null, core.doAction);
} else {
// 不清除对话框
core.doAction();
}
core.doAction();
};
////// 自定义事件时的点击操作 //////

View File

@ -876,6 +876,22 @@ control.prototype.setHeroOpacity = function (
////// 设置画布偏移
control.prototype.setGameCanvasTranslate = function (canvas, x, y) {
// Deprecated. Use RenderItem.transform instead.
// For editor compatibility.
var c = core.dom.gameCanvas[canvas];
x = x * core.domStyle.scale;
y = y * core.domStyle.scale;
c.style.transform = 'translate(' + x + 'px,' + y + 'px)';
c.style.webkitTransform = 'translate(' + x + 'px,' + y + 'px)';
c.style.OTransform = 'translate(' + x + 'px,' + y + 'px)';
c.style.MozTransform = 'translate(' + x + 'px,' + y + 'px)';
if (main.mode === 'editor' && editor.isMobile) {
c.style.transform =
'translate(' +
(x / core._PX_) * 96 +
'vw,' +
(y / core._PY_) * 96 +
'vw)';
}
};
////// 加减画布偏移

View File

@ -1563,109 +1563,71 @@ events.prototype.__action_doAsyncFunc = function (isAsync, func) {
}
};
events.prototype._action_text = function (data, x, y, prefix) {
events.prototype._action_text = function (data) {
if (this.__action_checkReplaying()) return;
const Store = Mota.require('@user/client-modules').TextboxStore;
const store = Store.get('main-textbox');
const { text } = data;
let title = '';
let inTitle = false;
let titleStartIndex = 0;
let titleEndIndex = 0;
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (inTitle) {
if (char === '\\' && text[i + 1] === ']') {
title += ']';
i++;
} else if (char === ']') {
inTitle = false;
titleEndIndex = i + 1;
break;
} else {
title += char;
}
continue;
}
if (char === '\t' && text[i + 1] === '[') {
inTitle = true;
titleStartIndex = i;
// 跳转至方括号内
i++;
continue;
}
if (char === '\\' && text[i + 1] === 't' && text[i + 2] === '[') {
inTitle = true;
titleStartIndex = i;
// 跳转至方括号内
i += 2;
continue;
}
const { textbox = 'main-textbox', text, icon = 'none', title = '' } = data;
const store = Store.get(textbox);
if (!store) {
core.doAction();
return;
}
const showText = text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
const loc = store.data.loc?.slice() ?? [];
loc[0] ??= 0;
loc[1] ??= 0;
loc[2] ??= 200;
loc[3] ??= 200;
const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data;
store.show();
store.modify({ title });
store.setText(showText);
// data.text = core.replaceText(data.text, prefix);
// var ctx = data.code ? '__text__' + data.code : null;
// data.ctx = ctx;
// if (core.getContextByName(ctx) && !data.showAll) {
// core.ui._animateUI('hide', ctx, function () {
// core.ui.drawTextBox(data.text, data);
// core.ui._animateUI('show', ctx, function () {
// if (data.async) core.doAction();
// });
// });
// return;
// }
// core.ui.drawTextBox(data.text, data);
// if (!data.showAll) {
// core.ui._animateUI('show', ctx, function () {
// if (data.async) core.doAction();
// });
// }
store.modify({
title,
icon,
x,
y,
width,
height,
loc: [x, y, width, height]
});
store.setText(text);
core.events.nowTextbox = textbox;
};
events.prototype._action_moveTextBox = function (data, x, y, prefix) {
events.prototype._action_autoText = function (data) {
if (this.__action_checkReplaying()) return;
this.__action_doAsyncFunc(
data.async,
core.moveTextBox,
data.code,
this.__action_getLoc(data.loc, x, y, prefix),
data.relative,
data.moveMode,
data.time
);
};
const Store = Mota.require('@user/client-modules').TextboxStore;
const { textbox = 'main-textbox', text, icon = 'none', title = '' } = data;
const store = Store.get(textbox);
if (!store) {
core.doAction();
return;
}
const loc = store.data.loc?.slice() ?? [];
loc[0] ??= 0;
loc[1] ??= 0;
loc[2] ??= 200;
loc[3] ??= 200;
const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data;
store.show();
store.modify({
title,
icon,
x,
y,
width,
height,
loc: [x, y, width, height]
});
store.setText(text);
events.prototype._action_clearTextBox = function (data, x, y, prefix) {
if (this.__action_checkReplaying()) return;
core.clearTextBox(data.code, core.doAction);
};
events.prototype._action_autoText = function (data, x, y, prefix) {
if (this.__action_checkReplaying()) return;
data.text = core.replaceText(data.text, prefix);
core.ui.drawTextBox(data.text);
setTimeout(core.doAction, data.time || 3000);
setTimeout(() => {
store.hide();
core.doAction();
}, data.time ?? 3000);
};
events.prototype._action_scrollText = function (data, x, y, prefix) {
if (this.__action_checkReplaying()) return;
data.text = core.replaceText(data.text, prefix);
this.__action_doAsyncFunc(
data.async,
core.drawScrollText,
data.text,
data.time || 5000,
data.lineHeight || 1.4
);
// todo: 2.B.1
};
events.prototype._action_comment = function (data, x, y, prefix) {
@ -1676,8 +1638,85 @@ events.prototype._action__label = function (data, x, y, prefix) {
core.doAction();
};
events.prototype._action_setText = function (data, x, y, prefix) {
this.setTextAttribute(data);
events.prototype._action_setText = function (data) {
const isNil = value => value === null || value === void 0;
const { textbox = 'main-textbox' } = data;
const Store = Mota.require('@user/client-modules').TextboxStore;
const { TextAlign, WordBreak } = Mota.require('@user/client-modules');
const Font = Mota.require('@motajs/render-style').Font;
const store = Store.get(textbox);
if (!store) {
core.doAction();
return;
}
// loc
const loc = store.data.loc?.slice() ?? [];
loc[0] ??= 0;
loc[1] ??= 0;
loc[2] ??= 200;
loc[3] ??= 200;
const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data;
const newLoc = [x, y, width, height];
// font
const { fontFamily, fontSize, fontWeight, fontItalic } = data;
const font = store.data.font ?? new Font();
const newFont = Font.clone(font, {
family: fontFamily,
size: fontSize,
weight: fontWeight,
italic: fontItalic
});
// config
const config = {
x,
y,
width,
height,
loc: newLoc,
font: newFont,
keepLast: data.keepLast,
interval: data.interval,
lineHeight: data.lineHeight,
fillStyle: data.fillStyle,
strokeStyle: data.strokeStyle,
strokeWidth: data.strokeWidth,
fill: isNil(data.fill),
stroke: !!data.stroke,
backColor: data.backColor,
winskin: data.winskin,
padding: data.padding,
titleFill: isNil(data.titleFill) ? 'gold' : 'transparent',
titleStroke: data.titleStroke ? 'black' : 'transparent',
titlePadding: data.titlePadding,
ignoreLineStart: data.ignoreLineStart,
ignoreLineEnd: data.ignoreLineEnd,
breakChars: data.breakChars
};
switch (data.textAlign) {
case 'left':
config.textAlign = TextAlign.Left;
break;
case 'center':
config.textAlign = TextAlign.Center;
break;
case 'right':
config.textAlign = TextAlign.Right;
break;
}
switch (data.wordBreak) {
case 'none':
config.wordBreak = WordBreak.None;
break;
case 'space':
config.wordBreak = WordBreak.Space;
break;
case 'all':
config.wordBreak = WordBreak.All;
break;
}
store.modify(config);
core.doAction();
};
@ -1686,6 +1725,29 @@ events.prototype._action_tip = function (data, x, y, prefix) {
core.doAction();
};
events.prototype._action_confirm = function (data, x, y, prefix) {
data.text = core.replaceText(data.text, prefix);
core.ui.drawConfirmBox(
data.text,
() => {
core.insertAction(data.yes ?? []);
core.doAction();
},
() => {
core.insertAction(data.no ?? []);
core.doAction();
}
);
};
events.prototype._action_choices = function (data, x, y, prefix) {
core.ui.drawChoices(
core.replaceText(data.text, prefix),
data.choices,
data.width
);
};
events.prototype._action_show = function (data, x, y, prefix) {
data.loc = this.__action_getLoc2D(data.loc, x, y, prefix);
if (data.time > 0 && data.floorId == core.status.floorId) {
@ -2519,14 +2581,6 @@ events.prototype._precompile_switch = function (data) {
return data;
};
events.prototype._action_choices = function (data, x, y, prefix) {
core.ui.drawChoices(
core.replaceText(data.text, prefix),
data.choices,
data.width
);
};
events.prototype.__action_choices_replaying = function (data, index) {
var selection = index;
if (index != 'none') {
@ -2580,21 +2634,6 @@ events.prototype._precompile_choices = function (data) {
return data;
};
events.prototype._action_confirm = function (data, x, y, prefix) {
data.text = core.replaceText(data.text, prefix);
core.ui.drawConfirmBox(
data.text,
() => {
core.insertAction(data.yes ?? []);
core.doAction();
},
() => {
core.insertAction(data.no ?? []);
core.doAction();
}
);
};
events.prototype.__action_confirm_replaying = function (data, index) {
if (index != 'none') {
var timeout = Math.floor(index / 100) || 0;

View File

@ -21,6 +21,7 @@ function main() {
this.dom = {
body: document.body,
gameDraw: document.getElementById('game-draw'),
gameCanvas: document.getElementsByClassName('gameCanvas'),
inputDiv: document.getElementById('inputDiv'),
inputMessage: document.getElementById('inputMessage'),
inputBox: document.getElementById('inputBox'),
@ -194,6 +195,10 @@ main.prototype.loadSync = function (mode, callback) {
};
main.prototype.loadAsync = async function (mode, callback) {
for (var i = 0; i < main.dom.gameCanvas.length; i++) {
main.canvas[main.dom.gameCanvas[i].id] =
main.dom.gameCanvas[i].getContext('2d');
}
main.mode = mode;
// 加载全塔属性代码

View File

@ -226,8 +226,14 @@ main.floors.MT14=
}
],
"8,2": [
"这一层有一个必须使用跳跃的地方需要1个跳跃次数你一共有3个跳跃次数请规划好",
"不要吐槽为什么天气变化这么大("
{
"type": "text",
"text": "这一层有一个必须使用跳跃的地方需要1个跳跃次数你一共有3个跳跃次数请规划好"
},
{
"type": "text",
"text": "不要吐槽为什么天气变化这么大("
}
],
"80,3": [
"你可以使用楼传传出去"

View File

@ -27,6 +27,8 @@ type ItemCls = 'tools' | 'items' | 'equips' | 'constants';
*/
type AllIds = keyof IdToNumber;
type AllIdsWithNone = AllIds | 'none';
/**
*
*/