@luna-editor/engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Player.d.ts +3 -0
- package/dist/Player.js +336 -0
- package/dist/atoms/screen-size.d.ts +22 -0
- package/dist/atoms/screen-size.js +8 -0
- package/dist/components/BacklogUI.d.ts +2 -0
- package/dist/components/BacklogUI.js +115 -0
- package/dist/components/ConversationLogUI.d.ts +2 -0
- package/dist/components/ConversationLogUI.js +115 -0
- package/dist/components/DebugControls.d.ts +12 -0
- package/dist/components/DebugControls.js +5 -0
- package/dist/components/DialogueBox.d.ts +2 -0
- package/dist/components/DialogueBox.js +28 -0
- package/dist/components/EndScreen.d.ts +8 -0
- package/dist/components/EndScreen.js +5 -0
- package/dist/components/GameScreen.d.ts +9 -0
- package/dist/components/GameScreen.js +111 -0
- package/dist/components/OverlayUI.d.ts +13 -0
- package/dist/components/OverlayUI.js +21 -0
- package/dist/components/PluginComponentProvider.d.ts +14 -0
- package/dist/components/PluginComponentProvider.js +24 -0
- package/dist/constants/screen-size.d.ts +3 -0
- package/dist/constants/screen-size.js +6 -0
- package/dist/contexts/DataContext.d.ts +24 -0
- package/dist/contexts/DataContext.js +101 -0
- package/dist/hooks/useBacklog.d.ts +14 -0
- package/dist/hooks/useBacklog.js +82 -0
- package/dist/hooks/useConversationLog.d.ts +14 -0
- package/dist/hooks/useConversationLog.js +82 -0
- package/dist/hooks/usePlayerLogic.d.ts +21 -0
- package/dist/hooks/usePlayerLogic.js +145 -0
- package/dist/hooks/usePluginAPI.d.ts +19 -0
- package/dist/hooks/usePluginAPI.js +42 -0
- package/dist/hooks/usePluginEvents.d.ts +14 -0
- package/dist/hooks/usePluginEvents.js +197 -0
- package/dist/hooks/usePreloadImages.d.ts +2 -0
- package/dist/hooks/usePreloadImages.js +56 -0
- package/dist/hooks/useScreenSize.d.ts +89 -0
- package/dist/hooks/useScreenSize.js +87 -0
- package/dist/hooks/useTypewriter.d.ts +11 -0
- package/dist/hooks/useTypewriter.js +56 -0
- package/dist/hooks/useUIVisibility.d.ts +9 -0
- package/dist/hooks/useUIVisibility.js +19 -0
- package/dist/hooks/useVoice.d.ts +4 -0
- package/dist/hooks/useVoice.js +21 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/plugin/PluginManager.d.ts +108 -0
- package/dist/plugin/PluginManager.js +851 -0
- package/dist/plugin/luna-react.d.ts +41 -0
- package/dist/plugin/luna-react.js +99 -0
- package/dist/sdk.d.ts +512 -0
- package/dist/sdk.js +64 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +2 -0
- package/dist/utils/attributeNormalizer.d.ts +5 -0
- package/dist/utils/attributeNormalizer.js +53 -0
- package/dist/utils/facePositionCalculator.d.ts +29 -0
- package/dist/utils/facePositionCalculator.js +127 -0
- package/package.json +55 -0
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// SDK exports for plugin development
|
|
3
|
+
// This file contains all types and utilities needed for plugin development
|
|
4
|
+
import React from "react";
|
|
5
|
+
// Component registration types
|
|
6
|
+
export var ComponentType;
|
|
7
|
+
(function (ComponentType) {
|
|
8
|
+
ComponentType["Backlog"] = "Backlog";
|
|
9
|
+
ComponentType["DialogueBox"] = "DialogueBox";
|
|
10
|
+
ComponentType["CharacterDisplay"] = "CharacterDisplay";
|
|
11
|
+
ComponentType["ActionMenu"] = "ActionMenu";
|
|
12
|
+
ComponentType["SaveLoadMenu"] = "SaveLoadMenu";
|
|
13
|
+
ComponentType["SaveMenu"] = "SaveMenu";
|
|
14
|
+
ComponentType["LoadMenu"] = "LoadMenu";
|
|
15
|
+
ComponentType["SettingsMenu"] = "SettingsMenu";
|
|
16
|
+
ComponentType["GameMenu"] = "GameMenu";
|
|
17
|
+
ComponentType["Config"] = "Config";
|
|
18
|
+
})(ComponentType || (ComponentType = {}));
|
|
19
|
+
// Component types are already exported as part of the interfaces above
|
|
20
|
+
// Plugin utility function
|
|
21
|
+
export function definePlugin(plugin) {
|
|
22
|
+
return plugin;
|
|
23
|
+
}
|
|
24
|
+
// Helper function for component registration
|
|
25
|
+
export function defineComponent(type, component) {
|
|
26
|
+
return { type, component };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Calculate effect position relative to face
|
|
30
|
+
* Utility function for plugins to position effects relative to character face
|
|
31
|
+
*/
|
|
32
|
+
export function calculateFaceRelativePosition(options) {
|
|
33
|
+
const { facePosition, relativePosition, offsetX = 0, offsetY = 0 } = options;
|
|
34
|
+
let baseX = facePosition.x;
|
|
35
|
+
let baseY = facePosition.y;
|
|
36
|
+
// Adjust base position based on relative position
|
|
37
|
+
switch (relativePosition) {
|
|
38
|
+
case "forehead":
|
|
39
|
+
baseY = facePosition.y - facePosition.height * 0.3;
|
|
40
|
+
break;
|
|
41
|
+
case "cheek_left":
|
|
42
|
+
baseX = facePosition.x - facePosition.width * 0.3;
|
|
43
|
+
break;
|
|
44
|
+
case "cheek_right":
|
|
45
|
+
baseX = facePosition.x + facePosition.width * 0.3;
|
|
46
|
+
break;
|
|
47
|
+
case "chin":
|
|
48
|
+
baseY = facePosition.y + facePosition.height * 0.3;
|
|
49
|
+
break;
|
|
50
|
+
case "center":
|
|
51
|
+
default:
|
|
52
|
+
// Use face center as is
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
x: baseX + offsetX,
|
|
57
|
+
y: baseY + offsetY,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Export React for plugin use
|
|
61
|
+
export { React };
|
|
62
|
+
export { OverlayUI } from "./components/OverlayUI";
|
|
63
|
+
// Screen size constants
|
|
64
|
+
export { aspectRatio, BasisHeight, BasisWidth } from "./constants/screen-size";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
export type ScenarioBlockType = "dialogue" | "narration" | "action_node" | "character_entrance" | "character_exit";
|
|
2
|
+
export interface ScenarioBlock {
|
|
3
|
+
id: string;
|
|
4
|
+
scenarioId: string;
|
|
5
|
+
order: number;
|
|
6
|
+
blockType: ScenarioBlockType;
|
|
7
|
+
content: string | null;
|
|
8
|
+
speakerId: string | null;
|
|
9
|
+
speakerStateId: string | null;
|
|
10
|
+
partVoiceId: string | null;
|
|
11
|
+
actionNodeId: string | null;
|
|
12
|
+
linkedExitBlockId: string | null;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
speaker?: {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
typeId: string;
|
|
19
|
+
workId: string;
|
|
20
|
+
description: string | null;
|
|
21
|
+
order: number;
|
|
22
|
+
} | null;
|
|
23
|
+
speakerState?: {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
imageUrl: string | null;
|
|
27
|
+
cropArea: string | null;
|
|
28
|
+
scale?: number;
|
|
29
|
+
translateY?: number;
|
|
30
|
+
translateX?: number;
|
|
31
|
+
} | null;
|
|
32
|
+
actionNode?: ActionNode | null;
|
|
33
|
+
attributeValues: ScenarioBlockAttributeValue[];
|
|
34
|
+
entityAttributeValues: ScenarioBlockEntityAttributeValue[];
|
|
35
|
+
characters?: ScenarioBlockCharacter[];
|
|
36
|
+
linkedExitBlock?: {
|
|
37
|
+
id: string;
|
|
38
|
+
order: number;
|
|
39
|
+
} | null;
|
|
40
|
+
}
|
|
41
|
+
export interface ScenarioBlockCharacter {
|
|
42
|
+
id: string;
|
|
43
|
+
scenarioBlockId: string;
|
|
44
|
+
objectId: string;
|
|
45
|
+
entityStateId: string;
|
|
46
|
+
positionX?: number | null;
|
|
47
|
+
positionY?: number | null;
|
|
48
|
+
zIndex?: number | null;
|
|
49
|
+
scale?: number | null;
|
|
50
|
+
object: {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
};
|
|
54
|
+
entityState: {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
imageUrl: string | null;
|
|
58
|
+
cropArea: string | null;
|
|
59
|
+
scale?: number;
|
|
60
|
+
translateY?: number;
|
|
61
|
+
translateX?: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface ActionNode {
|
|
65
|
+
id: string;
|
|
66
|
+
workId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
description: string | null;
|
|
69
|
+
createdAt: Date;
|
|
70
|
+
updatedAt: Date;
|
|
71
|
+
order: number;
|
|
72
|
+
pluginPackageName?: string | null;
|
|
73
|
+
nodeType?: string | null;
|
|
74
|
+
type?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ScenarioBlockAttributeValue {
|
|
77
|
+
scenarioBlockId: string;
|
|
78
|
+
attributeId: number;
|
|
79
|
+
value: string | null;
|
|
80
|
+
attribute: {
|
|
81
|
+
id: number;
|
|
82
|
+
name: string;
|
|
83
|
+
attributeType: string;
|
|
84
|
+
isNullable: boolean;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface ScenarioBlockEntityAttributeValue {
|
|
88
|
+
scenarioBlockId: string;
|
|
89
|
+
attributeId: number;
|
|
90
|
+
objectId: string;
|
|
91
|
+
entityStateId: string;
|
|
92
|
+
attribute: {
|
|
93
|
+
id: number;
|
|
94
|
+
name: string;
|
|
95
|
+
attributeType: string;
|
|
96
|
+
isNullable: boolean;
|
|
97
|
+
};
|
|
98
|
+
object: {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
};
|
|
102
|
+
entityState: {
|
|
103
|
+
id: string;
|
|
104
|
+
name: string;
|
|
105
|
+
imageUrl: string | null;
|
|
106
|
+
cropArea: string | null;
|
|
107
|
+
scale?: number;
|
|
108
|
+
translateY?: number;
|
|
109
|
+
translateX?: number;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export interface Character {
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
state?: EntityState;
|
|
116
|
+
element?: HTMLElement;
|
|
117
|
+
}
|
|
118
|
+
export interface EntityState {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
imageUrl?: string | null;
|
|
122
|
+
}
|
|
123
|
+
export interface DisplayedCharacter {
|
|
124
|
+
objectId: string;
|
|
125
|
+
entityStateId: string;
|
|
126
|
+
positionX?: number | null;
|
|
127
|
+
positionY?: number | null;
|
|
128
|
+
zIndex?: number | null;
|
|
129
|
+
scale?: number | null;
|
|
130
|
+
object: {
|
|
131
|
+
id: string;
|
|
132
|
+
name: string;
|
|
133
|
+
};
|
|
134
|
+
entityState: {
|
|
135
|
+
id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
imageUrl: string | null;
|
|
138
|
+
cropArea: string | null;
|
|
139
|
+
scale?: number;
|
|
140
|
+
translateY?: number;
|
|
141
|
+
translateX?: number;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export interface PublishedScenario {
|
|
145
|
+
id: string;
|
|
146
|
+
scenarioId: string;
|
|
147
|
+
workId: string;
|
|
148
|
+
name: string;
|
|
149
|
+
description: string | null;
|
|
150
|
+
blocks: ScenarioBlock[];
|
|
151
|
+
publishedAt: Date;
|
|
152
|
+
}
|
|
153
|
+
export interface PlayerSettings {
|
|
154
|
+
aspectRatio: string;
|
|
155
|
+
bgObjectFit: "contain" | "cover";
|
|
156
|
+
}
|
|
157
|
+
export interface PluginConfig {
|
|
158
|
+
packageName: string;
|
|
159
|
+
bundleUrl: string;
|
|
160
|
+
config?: unknown;
|
|
161
|
+
}
|
|
162
|
+
export interface PlayerProps {
|
|
163
|
+
scenario: PublishedScenario;
|
|
164
|
+
settings?: PlayerSettings;
|
|
165
|
+
plugins?: PluginConfig[];
|
|
166
|
+
onEnd?: () => void;
|
|
167
|
+
onScenarioEnd?: () => void;
|
|
168
|
+
onScenarioStart?: () => void;
|
|
169
|
+
onScenarioCancelled?: () => void;
|
|
170
|
+
className?: string;
|
|
171
|
+
autoplay?: boolean;
|
|
172
|
+
}
|
|
173
|
+
export interface PlayerState {
|
|
174
|
+
currentBlockIndex: number;
|
|
175
|
+
isPlaying: boolean;
|
|
176
|
+
isEnded: boolean;
|
|
177
|
+
}
|
|
178
|
+
export interface BacklogEntry {
|
|
179
|
+
id: string;
|
|
180
|
+
timestamp: number;
|
|
181
|
+
blockIndex: number;
|
|
182
|
+
blockType: string;
|
|
183
|
+
content: string | null;
|
|
184
|
+
speakerName?: string;
|
|
185
|
+
speakerState?: string;
|
|
186
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize attribute values from database format to plugin-friendly format
|
|
3
|
+
*/
|
|
4
|
+
export function normalizeAttributes(block) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const attributes = {};
|
|
7
|
+
// Process regular attributes (text, number, select, etc.)
|
|
8
|
+
(_a = block.attributeValues) === null || _a === void 0 ? void 0 : _a.forEach((attrValue) => {
|
|
9
|
+
var _a;
|
|
10
|
+
if (((_a = attrValue === null || attrValue === void 0 ? void 0 : attrValue.attribute) === null || _a === void 0 ? void 0 : _a.name) && attrValue.value !== undefined) {
|
|
11
|
+
const value = normalizeAttributeValue(attrValue.value, attrValue.attribute.attributeType);
|
|
12
|
+
attributes[attrValue.attribute.name] = value;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
// Process entity attributes (entity type)
|
|
16
|
+
(_b = block.entityAttributeValues) === null || _b === void 0 ? void 0 : _b.forEach((entityAttr) => {
|
|
17
|
+
var _a;
|
|
18
|
+
if ((_a = entityAttr === null || entityAttr === void 0 ? void 0 : entityAttr.attribute) === null || _a === void 0 ? void 0 : _a.name) {
|
|
19
|
+
// For entity attributes, provide rich object instead of just ID
|
|
20
|
+
attributes[entityAttr.attribute.name] = entityAttr.objectId;
|
|
21
|
+
// Also provide detailed entity info with _detail suffix
|
|
22
|
+
attributes[`${entityAttr.attribute.name}_detail`] = {
|
|
23
|
+
objectId: entityAttr.objectId,
|
|
24
|
+
object: entityAttr.object,
|
|
25
|
+
entityState: entityAttr.entityState,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return attributes;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalize individual attribute value based on its type
|
|
33
|
+
*/
|
|
34
|
+
function normalizeAttributeValue(value, type) {
|
|
35
|
+
if (value === null || value === undefined) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
switch (type) {
|
|
39
|
+
case "number":
|
|
40
|
+
return Number(value);
|
|
41
|
+
case "select":
|
|
42
|
+
return String(value);
|
|
43
|
+
case "text":
|
|
44
|
+
case "textarea":
|
|
45
|
+
return String(value || "");
|
|
46
|
+
case "json":
|
|
47
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
48
|
+
case "array":
|
|
49
|
+
return Array.isArray(value) ? value : [value];
|
|
50
|
+
default:
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface CropArea {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface FacePosition {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
export interface EntityState {
|
|
14
|
+
cropArea: string | null;
|
|
15
|
+
scale?: number;
|
|
16
|
+
translateX?: number;
|
|
17
|
+
translateY?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Calculate face position from character element and entity state
|
|
21
|
+
*/
|
|
22
|
+
export declare function calculateFacePosition(characterElement: HTMLElement, entityState: EntityState): FacePosition | null;
|
|
23
|
+
/**
|
|
24
|
+
* Calculate effect position relative to face
|
|
25
|
+
*/
|
|
26
|
+
export declare function calculateEffectPosition(facePosition: FacePosition, relativePosition: "forehead" | "cheek_left" | "cheek_right" | "chin" | "center", offsetX?: number, offsetY?: number): {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Face position calculation utility for emotion effects
|
|
2
|
+
// Calculates face position from EntityState cropArea data
|
|
3
|
+
/**
|
|
4
|
+
* Parse cropArea JSON string to CropArea object
|
|
5
|
+
*/
|
|
6
|
+
function parseCropArea(cropAreaString) {
|
|
7
|
+
if (!cropAreaString)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(cropAreaString);
|
|
11
|
+
if (typeof parsed === "object" &&
|
|
12
|
+
typeof parsed.x === "number" &&
|
|
13
|
+
typeof parsed.y === "number" &&
|
|
14
|
+
typeof parsed.width === "number" &&
|
|
15
|
+
typeof parsed.height === "number") {
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
catch (_a) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calculate face position from character element and entity state
|
|
26
|
+
*/
|
|
27
|
+
export function calculateFacePosition(characterElement, entityState) {
|
|
28
|
+
const cropArea = parseCropArea(entityState.cropArea);
|
|
29
|
+
if (!cropArea)
|
|
30
|
+
return null;
|
|
31
|
+
// Get character element's actual display size
|
|
32
|
+
const elementRect = characterElement.getBoundingClientRect();
|
|
33
|
+
const elementWidth = elementRect.width;
|
|
34
|
+
const elementHeight = elementRect.height;
|
|
35
|
+
// Get the image element inside character element
|
|
36
|
+
const imgElement = characterElement.querySelector("img");
|
|
37
|
+
if (!imgElement)
|
|
38
|
+
return null;
|
|
39
|
+
// Get natural image dimensions
|
|
40
|
+
const naturalWidth = imgElement.naturalWidth;
|
|
41
|
+
const naturalHeight = imgElement.naturalHeight;
|
|
42
|
+
if (naturalWidth === 0 || naturalHeight === 0)
|
|
43
|
+
return null;
|
|
44
|
+
// Calculate how the image is displayed (considering object-fit: contain/cover)
|
|
45
|
+
const computedStyle = window.getComputedStyle(imgElement);
|
|
46
|
+
const objectFit = computedStyle.objectFit || "contain";
|
|
47
|
+
// Calculate display dimensions based on object-fit
|
|
48
|
+
let displayWidth;
|
|
49
|
+
let displayHeight;
|
|
50
|
+
let offsetX = 0;
|
|
51
|
+
let offsetY = 0;
|
|
52
|
+
if (objectFit === "contain") {
|
|
53
|
+
// Image is scaled to fit within the element while maintaining aspect ratio
|
|
54
|
+
const elementAspect = elementWidth / elementHeight;
|
|
55
|
+
const imageAspect = naturalWidth / naturalHeight;
|
|
56
|
+
if (imageAspect > elementAspect) {
|
|
57
|
+
// Image is wider - fit to width
|
|
58
|
+
displayWidth = elementWidth;
|
|
59
|
+
displayHeight = elementWidth / imageAspect;
|
|
60
|
+
offsetY = (elementHeight - displayHeight) / 2;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Image is taller - fit to height
|
|
64
|
+
displayHeight = elementHeight;
|
|
65
|
+
displayWidth = elementHeight * imageAspect;
|
|
66
|
+
offsetX = (elementWidth - displayWidth) / 2;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// For other object-fit values, assume full element size
|
|
71
|
+
displayWidth = elementWidth;
|
|
72
|
+
displayHeight = elementHeight;
|
|
73
|
+
}
|
|
74
|
+
// Calculate scale factor from natural to displayed image
|
|
75
|
+
const scaleX = displayWidth / naturalWidth;
|
|
76
|
+
const scaleY = displayHeight / naturalHeight;
|
|
77
|
+
// Apply entity state transformations
|
|
78
|
+
const entityScale = entityState.scale || 1.0;
|
|
79
|
+
const entityTranslateX = entityState.translateX || 0;
|
|
80
|
+
const entityTranslateY = entityState.translateY || 0;
|
|
81
|
+
// Calculate face position in displayed coordinates
|
|
82
|
+
const faceDisplayWidth = cropArea.width * scaleX * entityScale;
|
|
83
|
+
const faceDisplayHeight = cropArea.height * scaleY * entityScale;
|
|
84
|
+
// Face center position
|
|
85
|
+
const faceCenterX = offsetX +
|
|
86
|
+
(cropArea.x + cropArea.width / 2) * scaleX * entityScale +
|
|
87
|
+
entityTranslateX;
|
|
88
|
+
const faceCenterY = offsetY +
|
|
89
|
+
(cropArea.y + cropArea.height / 2) * scaleY * entityScale +
|
|
90
|
+
entityTranslateY;
|
|
91
|
+
return {
|
|
92
|
+
x: faceCenterX,
|
|
93
|
+
y: faceCenterY,
|
|
94
|
+
width: faceDisplayWidth,
|
|
95
|
+
height: faceDisplayHeight,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Calculate effect position relative to face
|
|
100
|
+
*/
|
|
101
|
+
export function calculateEffectPosition(facePosition, relativePosition, offsetX = 0, offsetY = 0) {
|
|
102
|
+
let baseX = facePosition.x;
|
|
103
|
+
let baseY = facePosition.y;
|
|
104
|
+
// Adjust base position based on relative position
|
|
105
|
+
switch (relativePosition) {
|
|
106
|
+
case "forehead":
|
|
107
|
+
baseY = facePosition.y - facePosition.height * 0.3;
|
|
108
|
+
break;
|
|
109
|
+
case "cheek_left":
|
|
110
|
+
baseX = facePosition.x - facePosition.width * 0.3;
|
|
111
|
+
break;
|
|
112
|
+
case "cheek_right":
|
|
113
|
+
baseX = facePosition.x + facePosition.width * 0.3;
|
|
114
|
+
break;
|
|
115
|
+
case "chin":
|
|
116
|
+
baseY = facePosition.y + facePosition.height * 0.3;
|
|
117
|
+
break;
|
|
118
|
+
case "center":
|
|
119
|
+
default:
|
|
120
|
+
// Use face center as is
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
x: baseX + offsetX,
|
|
125
|
+
y: baseY + offsetY,
|
|
126
|
+
};
|
|
127
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luna-editor/engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Luna Editor scenario playback engine",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./sdk": {
|
|
16
|
+
"types": "./dist/sdk.d.ts",
|
|
17
|
+
"default": "./dist/sdk.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
27
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"clsx": "^2.1.1",
|
|
31
|
+
"lucide-react": "^0.503.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react": "^19",
|
|
35
|
+
"@types/react-dom": "^19",
|
|
36
|
+
"typescript": "^5",
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-dom": "^19.0.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/instansys/luna-editor.git",
|
|
46
|
+
"directory": "packages/engine"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"scenario",
|
|
50
|
+
"playback",
|
|
51
|
+
"visual-novel",
|
|
52
|
+
"game-engine"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT"
|
|
55
|
+
}
|