优化座驾特效入场标题
This commit is contained in:
@@ -39,6 +39,8 @@ class EffectBroadcast implements ShouldBroadcastNow
|
|||||||
* @param string $operator 触发特效的用户名(购买者)
|
* @param string $operator 触发特效的用户名(购买者)
|
||||||
* @param string|null $targetUsername 接收者用户名(null = 全员)
|
* @param string|null $targetUsername 接收者用户名(null = 全员)
|
||||||
* @param string|null $giftMessage 附带赠言
|
* @param string|null $giftMessage 附带赠言
|
||||||
|
* @param string|null $effectTitle 特效画面标题
|
||||||
|
* @param string|null $rideName 座驾名称
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly int $roomId,
|
public readonly int $roomId,
|
||||||
@@ -46,6 +48,8 @@ class EffectBroadcast implements ShouldBroadcastNow
|
|||||||
public readonly string $operator,
|
public readonly string $operator,
|
||||||
public readonly ?string $targetUsername = null,
|
public readonly ?string $targetUsername = null,
|
||||||
public readonly ?string $giftMessage = null,
|
public readonly ?string $giftMessage = null,
|
||||||
|
public readonly ?string $effectTitle = null,
|
||||||
|
public readonly ?string $rideName = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +77,8 @@ class EffectBroadcast implements ShouldBroadcastNow
|
|||||||
'operator' => $this->operator,
|
'operator' => $this->operator,
|
||||||
'target_username' => $this->targetUsername, // null = 全员
|
'target_username' => $this->targetUsername, // null = 全员
|
||||||
'gift_message' => $this->giftMessage,
|
'gift_message' => $this->giftMessage,
|
||||||
|
'effect_title' => $this->effectTitle,
|
||||||
|
'ride_name' => $this->rideName,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ class ChatController extends Controller
|
|||||||
// 3. 广播和初始化欢迎(仅限初次进入)
|
// 3. 广播和初始化欢迎(仅限初次进入)
|
||||||
$newbieEffect = null;
|
$newbieEffect = null;
|
||||||
$initialRideEffect = null;
|
$initialRideEffect = null;
|
||||||
|
$initialRideEffectOptions = null;
|
||||||
$initialPresenceTheme = null;
|
$initialPresenceTheme = null;
|
||||||
$initialWelcomeMessage = null;
|
$initialWelcomeMessage = null;
|
||||||
$initialWelcomeMessages = [];
|
$initialWelcomeMessages = [];
|
||||||
@@ -246,15 +247,27 @@ class ChatController extends Controller
|
|||||||
'welcome_kind' => 'ride_presence',
|
'welcome_kind' => 'ride_presence',
|
||||||
'ride_key' => $ridePresencePayload['ride_key'],
|
'ride_key' => $ridePresencePayload['ride_key'],
|
||||||
'ride_name' => $ridePresencePayload['ride_name'],
|
'ride_name' => $ridePresencePayload['ride_name'],
|
||||||
|
'effect_title' => $ridePresencePayload['effect_title'],
|
||||||
'sent_at' => now()->toDateTimeString(),
|
'sent_at' => now()->toDateTimeString(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 座驾进场独立追加一条播报,并广播全屏特效给其他在线用户。
|
// 座驾进场独立追加一条播报,并广播全屏特效给其他在线用户。
|
||||||
$this->chatState->pushMessage($id, $rideWelcomeMsg);
|
$this->chatState->pushMessage($id, $rideWelcomeMsg);
|
||||||
broadcast(new MessageSent($id, $rideWelcomeMsg));
|
broadcast(new MessageSent($id, $rideWelcomeMsg));
|
||||||
broadcast(new \App\Events\EffectBroadcast($id, $ridePresencePayload['ride_key'], $user->username))->toOthers();
|
broadcast(new \App\Events\EffectBroadcast(
|
||||||
|
$id,
|
||||||
|
$ridePresencePayload['ride_key'],
|
||||||
|
$user->username,
|
||||||
|
effectTitle: $ridePresencePayload['effect_title'],
|
||||||
|
rideName: $ridePresencePayload['ride_name'],
|
||||||
|
))->toOthers();
|
||||||
|
|
||||||
$initialRideEffect = $ridePresencePayload['ride_key'];
|
$initialRideEffect = $ridePresencePayload['ride_key'];
|
||||||
|
$initialRideEffectOptions = [
|
||||||
|
'effect_title' => $ridePresencePayload['effect_title'],
|
||||||
|
'ride_name' => $ridePresencePayload['ride_name'],
|
||||||
|
'operator' => $user->username,
|
||||||
|
];
|
||||||
$initialWelcomeMessages[] = $rideWelcomeMsg;
|
$initialWelcomeMessages[] = $rideWelcomeMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +358,7 @@ class ChatController extends Controller
|
|||||||
'weekEffect' => $this->shopService->getActiveWeekEffect($user),
|
'weekEffect' => $this->shopService->getActiveWeekEffect($user),
|
||||||
'newbieEffect' => $newbieEffect,
|
'newbieEffect' => $newbieEffect,
|
||||||
'initialRideEffect' => $initialRideEffect,
|
'initialRideEffect' => $initialRideEffect,
|
||||||
|
'initialRideEffectOptions' => $initialRideEffectOptions,
|
||||||
'initialPresenceTheme' => $initialPresenceTheme,
|
'initialPresenceTheme' => $initialPresenceTheme,
|
||||||
'initialWelcomeMessage' => $initialWelcomeMessage,
|
'initialWelcomeMessage' => $initialWelcomeMessage,
|
||||||
'initialWelcomeMessages' => $initialWelcomeMessages,
|
'initialWelcomeMessages' => $initialWelcomeMessages,
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ class RideService
|
|||||||
'ride_key' => $rideKey,
|
'ride_key' => $rideKey,
|
||||||
'ride_name' => $item->name,
|
'ride_name' => $item->name,
|
||||||
'ride_icon' => (string) ($item->icon ?? '🚘'),
|
'ride_icon' => (string) ($item->icon ?? '🚘'),
|
||||||
|
'effect_title' => "{$user->username} 乘坐【{$item->name}】闪亮登场",
|
||||||
'welcome_text' => ChatContentSanitizer::htmlText($rendered),
|
'welcome_text' => ChatContentSanitizer::htmlText($rendered),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,10 +482,15 @@ export function bindChatEvents() {
|
|||||||
const target = e.detail?.target_username;
|
const target = e.detail?.target_username;
|
||||||
const operator = e.detail?.operator;
|
const operator = e.detail?.operator;
|
||||||
const myName = window.chatContext?.username;
|
const myName = window.chatContext?.username;
|
||||||
|
const effectOptions = {
|
||||||
|
effect_title: e.detail?.effect_title,
|
||||||
|
ride_name: e.detail?.ride_name,
|
||||||
|
operator,
|
||||||
|
};
|
||||||
|
|
||||||
if (type && typeof EffectManager !== "undefined") {
|
if (type && typeof EffectManager !== "undefined") {
|
||||||
if (!target || target === myName || operator === myName) {
|
if (!target || target === myName || operator === myName) {
|
||||||
EffectManager.play(type);
|
EffectManager.play(type, effectOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function playEntryEffect(initialState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
window.EffectManager?.play?.(initialState.entryEffect);
|
window.EffectManager?.play?.(initialState.entryEffect, initialState.entryEffectOptions || {});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -446,8 +446,9 @@ const Type99AEffect = (() => {
|
|||||||
* @param {number} w 画布宽度
|
* @param {number} w 画布宽度
|
||||||
* @param {number} h 画布高度
|
* @param {number} h 画布高度
|
||||||
* @param {number} progress 播放进度
|
* @param {number} progress 播放进度
|
||||||
|
* @param {string} title 入场标题
|
||||||
*/
|
*/
|
||||||
function drawHud(ctx, w, h, progress) {
|
function drawHud(ctx, w, h, progress, title) {
|
||||||
const enter = Math.min(1, Math.max(0, (progress - 0.14) / 0.2));
|
const enter = Math.min(1, Math.max(0, (progress - 0.14) / 0.2));
|
||||||
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.16));
|
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.16));
|
||||||
const alpha = easeInOutSine(enter) * leave;
|
const alpha = easeInOutSine(enter) * leave;
|
||||||
@@ -461,7 +462,7 @@ const Type99AEffect = (() => {
|
|||||||
ctx.fillStyle = "rgba(28,25,23,0.66)";
|
ctx.fillStyle = "rgba(28,25,23,0.66)";
|
||||||
ctx.strokeStyle = "rgba(253,230,138,0.72)";
|
ctx.strokeStyle = "rgba(253,230,138,0.72)";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
roundRect(ctx, w * 0.5 - 226, y - 42, 452, 88, 18);
|
roundRect(ctx, w * 0.5 - 320, y - 46, 640, 96, 18);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
@@ -469,8 +470,8 @@ const Type99AEffect = (() => {
|
|||||||
ctx.font = "700 16px serif";
|
ctx.font = "700 16px serif";
|
||||||
ctx.fillText("ZTZ-99A ARMORED FORCE", w * 0.5, y - 12);
|
ctx.fillText("ZTZ-99A ARMORED FORCE", w * 0.5, y - 12);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "900 40px serif";
|
ctx.font = "900 38px serif";
|
||||||
ctx.fillText("99A主战坦克 重装入场", w * 0.5, y + 28);
|
ctx.fillText(title, w * 0.5, y + 28, 590);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,13 +504,15 @@ const Type99AEffect = (() => {
|
|||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
||||||
* @param {Function} onEnd 结束回调
|
* @param {Function} onEnd 结束回调
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
* @returns {{cancel: Function}}
|
* @returns {{cancel: Function}}
|
||||||
*/
|
*/
|
||||||
function start(canvas, onEnd) {
|
function start(canvas, onEnd, options = {}) {
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
const dust = createDust(w, h);
|
const dust = createDust(w, h);
|
||||||
|
const title = String(options.effect_title || "99A主战坦克 重装入场").trim() || "99A主战坦克 重装入场";
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
let animId = null;
|
let animId = null;
|
||||||
let finished = false;
|
let finished = false;
|
||||||
@@ -553,7 +556,7 @@ const Type99AEffect = (() => {
|
|||||||
drawDust(ctx, dust, w, progress);
|
drawDust(ctx, dust, w, progress);
|
||||||
drawShockwave(ctx, w, h, progress);
|
drawShockwave(ctx, w, h, progress);
|
||||||
drawTank(ctx, tankX, tankY, scale, progress);
|
drawTank(ctx, tankX, tankY, scale, progress);
|
||||||
drawHud(ctx, w, h, progress);
|
drawHud(ctx, w, h, progress, title);
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
animId = requestAnimationFrame(animate);
|
animId = requestAnimationFrame(animate);
|
||||||
|
|||||||
@@ -269,8 +269,9 @@ const Df5cEffect = (() => {
|
|||||||
* @param {number} w 画布宽度
|
* @param {number} w 画布宽度
|
||||||
* @param {number} h 画布高度
|
* @param {number} h 画布高度
|
||||||
* @param {number} progress 播放进度
|
* @param {number} progress 播放进度
|
||||||
|
* @param {string} title 入场标题
|
||||||
*/
|
*/
|
||||||
function drawHud(ctx, w, h, progress) {
|
function drawHud(ctx, w, h, progress, title) {
|
||||||
const enter = Math.min(1, Math.max(0, (progress - 0.1) / 0.18));
|
const enter = Math.min(1, Math.max(0, (progress - 0.1) / 0.18));
|
||||||
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.14));
|
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.14));
|
||||||
const alpha = easeInOutCubic(enter) * leave;
|
const alpha = easeInOutCubic(enter) * leave;
|
||||||
@@ -282,7 +283,7 @@ const Df5cEffect = (() => {
|
|||||||
ctx.fillStyle = "rgba(15,23,42,0.68)";
|
ctx.fillStyle = "rgba(15,23,42,0.68)";
|
||||||
ctx.strokeStyle = "rgba(248,113,113,0.72)";
|
ctx.strokeStyle = "rgba(248,113,113,0.72)";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
roundRect(ctx, w * 0.5 - 246, y - 42, 492, 88, 18);
|
roundRect(ctx, w * 0.5 - 330, y - 46, 660, 96, 18);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.shadowColor = "rgba(248,113,113,0.95)";
|
ctx.shadowColor = "rgba(248,113,113,0.95)";
|
||||||
@@ -292,7 +293,7 @@ const Df5cEffect = (() => {
|
|||||||
ctx.fillText("DF-5C STRATEGIC LAUNCH PREVIEW", w * 0.5, y - 12);
|
ctx.fillText("DF-5C STRATEGIC LAUNCH PREVIEW", w * 0.5, y - 12);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "900 38px serif";
|
ctx.font = "900 38px serif";
|
||||||
ctx.fillText("东风-5C 洲际导弹 升空", w * 0.5, y + 28);
|
ctx.fillText(title, w * 0.5, y + 28, 610);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,13 +326,15 @@ const Df5cEffect = (() => {
|
|||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
||||||
* @param {Function} onEnd 结束回调
|
* @param {Function} onEnd 结束回调
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
* @returns {{cancel: Function}}
|
* @returns {{cancel: Function}}
|
||||||
*/
|
*/
|
||||||
function start(canvas, onEnd) {
|
function start(canvas, onEnd, options = {}) {
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
const particles = createParticles(120);
|
const particles = createParticles(120);
|
||||||
|
const title = String(options.effect_title || "东风-5C 洲际导弹 升空").trim() || "东风-5C 洲际导弹 升空";
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
let animId = null;
|
let animId = null;
|
||||||
let finished = false;
|
let finished = false;
|
||||||
@@ -375,7 +378,7 @@ const Df5cEffect = (() => {
|
|||||||
drawLaunchPad(ctx, w, h, progress);
|
drawLaunchPad(ctx, w, h, progress);
|
||||||
drawExhaust(ctx, particles, tailX, tailY, progress);
|
drawExhaust(ctx, particles, tailX, tailY, progress);
|
||||||
drawMissile(ctx, launchX, launchY, scale, progress);
|
drawMissile(ctx, launchX, launchY, scale, progress);
|
||||||
drawHud(ctx, w, h, progress);
|
drawHud(ctx, w, h, progress, title);
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
animId = requestAnimationFrame(animate);
|
animId = requestAnimationFrame(animate);
|
||||||
|
|||||||
@@ -221,9 +221,9 @@ const EffectManager = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (playNext) {
|
if (playNext) {
|
||||||
const nextType = _dequeueNextType();
|
const nextEffect = _dequeueNextType();
|
||||||
if (nextType) {
|
if (nextEffect) {
|
||||||
play(nextType);
|
play(nextEffect.type, nextEffect.options || {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,8 +232,9 @@ const EffectManager = (() => {
|
|||||||
* 将特效加入有限队列,同类型短时间重复触发时只保留一份。
|
* 将特效加入有限队列,同类型短时间重复触发时只保留一份。
|
||||||
*
|
*
|
||||||
* @param {string} type 待播放特效类型
|
* @param {string} type 待播放特效类型
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
*/
|
*/
|
||||||
function _enqueue(type) {
|
function _enqueue(type, options = {}) {
|
||||||
const existingIndex = _queue.findIndex((item) => item.type === type);
|
const existingIndex = _queue.findIndex((item) => item.type === type);
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
_queue.splice(existingIndex, 1);
|
_queue.splice(existingIndex, 1);
|
||||||
@@ -241,6 +242,7 @@ const EffectManager = (() => {
|
|||||||
|
|
||||||
_queue.push({
|
_queue.push({
|
||||||
type,
|
type,
|
||||||
|
options,
|
||||||
queuedAt: Date.now(),
|
queuedAt: Date.now(),
|
||||||
keepUntilPlayed: type === "wedding-fireworks",
|
keepUntilPlayed: type === "wedding-fireworks",
|
||||||
});
|
});
|
||||||
@@ -252,7 +254,7 @@ const EffectManager = (() => {
|
|||||||
/**
|
/**
|
||||||
* 取出下一个仍然有效的排队特效。
|
* 取出下一个仍然有效的排队特效。
|
||||||
*
|
*
|
||||||
* @returns {string|null}
|
* @returns {{type: string, options: object}|null}
|
||||||
*/
|
*/
|
||||||
function _dequeueNextType() {
|
function _dequeueNextType() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -260,7 +262,7 @@ const EffectManager = (() => {
|
|||||||
while (_queue.length > 0) {
|
while (_queue.length > 0) {
|
||||||
const next = _queue.shift();
|
const next = _queue.shift();
|
||||||
if (next.keepUntilPlayed || now - next.queuedAt <= QUEUED_EFFECT_TTL) {
|
if (next.keepUntilPlayed || now - next.queuedAt <= QUEUED_EFFECT_TTL) {
|
||||||
return next.type;
|
return next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,14 +332,15 @@ const EffectManager = (() => {
|
|||||||
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
||||||
* @param {Function} finishCurrent 当前特效结束回调
|
* @param {Function} finishCurrent 当前特效结束回调
|
||||||
* @param {string} startMethod 启动方法名称
|
* @param {string} startMethod 启动方法名称
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
* @returns {boolean} 是否成功找到并启动特效
|
* @returns {boolean} 是否成功找到并启动特效
|
||||||
*/
|
*/
|
||||||
function _startEffect(effectObject, canvas, finishCurrent, startMethod = "start") {
|
function _startEffect(effectObject, canvas, finishCurrent, startMethod = "start", options = {}) {
|
||||||
if (!effectObject || typeof effectObject[startMethod] !== "function") {
|
if (!effectObject || typeof effectObject[startMethod] !== "function") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_bindEffectController(effectObject[startMethod](canvas, finishCurrent));
|
_bindEffectController(effectObject[startMethod](canvas, finishCurrent, options));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,8 +348,9 @@ const EffectManager = (() => {
|
|||||||
* 播放指定特效
|
* 播放指定特效
|
||||||
*
|
*
|
||||||
* @param {string} type 特效类型:fireworks / rain / lightning / snow / sakura / meteors / gold-rain / hearts / confetti / fireflies / j35 / 99a / df5c / fujian
|
* @param {string} type 特效类型:fireworks / rain / lightning / snow / sakura / meteors / gold-rain / hearts / confetti / fireflies / j35 / 99a / df5c / fujian
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
*/
|
*/
|
||||||
function play(type) {
|
function play(type, options = {}) {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -358,19 +362,20 @@ const EffectManager = (() => {
|
|||||||
|
|
||||||
// 防重入:同时只允许一个特效
|
// 防重入:同时只允许一个特效
|
||||||
if (_current) {
|
if (_current) {
|
||||||
_enqueue(type);
|
_enqueue(type, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_play(type);
|
_play(type, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载模块后播放指定特效。
|
* 加载模块后播放指定特效。
|
||||||
*
|
*
|
||||||
* @param {string} type 特效类型
|
* @param {string} type 特效类型
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
*/
|
*/
|
||||||
async function _play(type) {
|
async function _play(type, options = {}) {
|
||||||
_current = type;
|
_current = type;
|
||||||
const token = _playToken;
|
const token = _playToken;
|
||||||
|
|
||||||
@@ -439,16 +444,16 @@ const EffectManager = (() => {
|
|||||||
started = _startEffect(window.FirefliesEffect, canvas, finishCurrent);
|
started = _startEffect(window.FirefliesEffect, canvas, finishCurrent);
|
||||||
break;
|
break;
|
||||||
case "j35":
|
case "j35":
|
||||||
started = _startEffect(window.J35Effect, canvas, finishCurrent);
|
started = _startEffect(window.J35Effect, canvas, finishCurrent, "start", options);
|
||||||
break;
|
break;
|
||||||
case "99a":
|
case "99a":
|
||||||
started = _startEffect(window.Type99AEffect, canvas, finishCurrent);
|
started = _startEffect(window.Type99AEffect, canvas, finishCurrent, "start", options);
|
||||||
break;
|
break;
|
||||||
case "df5c":
|
case "df5c":
|
||||||
started = _startEffect(window.Df5cEffect, canvas, finishCurrent);
|
started = _startEffect(window.Df5cEffect, canvas, finishCurrent, "start", options);
|
||||||
break;
|
break;
|
||||||
case "fujian":
|
case "fujian":
|
||||||
started = _startEffect(window.FujianEffect, canvas, finishCurrent);
|
started = _startEffect(window.FujianEffect, canvas, finishCurrent, "start", options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`[EffectManager] 未知特效类型:${type}`);
|
console.warn(`[EffectManager] 未知特效类型:${type}`);
|
||||||
|
|||||||
@@ -393,8 +393,9 @@ const FujianEffect = (() => {
|
|||||||
* @param {number} w 画布宽度
|
* @param {number} w 画布宽度
|
||||||
* @param {number} h 画布高度
|
* @param {number} h 画布高度
|
||||||
* @param {number} progress 播放进度
|
* @param {number} progress 播放进度
|
||||||
|
* @param {string} title 入场标题
|
||||||
*/
|
*/
|
||||||
function drawHud(ctx, w, h, progress) {
|
function drawHud(ctx, w, h, progress, title) {
|
||||||
const enter = Math.min(1, Math.max(0, (progress - 0.12) / 0.2));
|
const enter = Math.min(1, Math.max(0, (progress - 0.12) / 0.2));
|
||||||
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.14));
|
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.14));
|
||||||
const alpha = easeInOutCubic(enter) * leave;
|
const alpha = easeInOutCubic(enter) * leave;
|
||||||
@@ -406,7 +407,7 @@ const FujianEffect = (() => {
|
|||||||
ctx.fillStyle = "rgba(15,23,42,0.68)";
|
ctx.fillStyle = "rgba(15,23,42,0.68)";
|
||||||
ctx.strokeStyle = "rgba(103,232,249,0.72)";
|
ctx.strokeStyle = "rgba(103,232,249,0.72)";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
roundRect(ctx, w * 0.5 - 236, y - 42, 472, 88, 18);
|
roundRect(ctx, w * 0.5 - 320, y - 46, 640, 96, 18);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.shadowColor = "rgba(103,232,249,0.95)";
|
ctx.shadowColor = "rgba(103,232,249,0.95)";
|
||||||
@@ -416,7 +417,7 @@ const FujianEffect = (() => {
|
|||||||
ctx.fillText("FUJIAN AIRCRAFT CARRIER PREVIEW", w * 0.5, y - 12);
|
ctx.fillText("FUJIAN AIRCRAFT CARRIER PREVIEW", w * 0.5, y - 12);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "900 38px serif";
|
ctx.font = "900 38px serif";
|
||||||
ctx.fillText("福建舰 航母入场", w * 0.5, y + 28);
|
ctx.fillText(title, w * 0.5, y + 28, 590);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,13 +450,15 @@ const FujianEffect = (() => {
|
|||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
||||||
* @param {Function} onEnd 结束回调
|
* @param {Function} onEnd 结束回调
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
* @returns {{cancel: Function}}
|
* @returns {{cancel: Function}}
|
||||||
*/
|
*/
|
||||||
function start(canvas, onEnd) {
|
function start(canvas, onEnd, options = {}) {
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
const waves = createWaves(w, h);
|
const waves = createWaves(w, h);
|
||||||
|
const title = String(options.effect_title || "福建舰 航母入场").trim() || "福建舰 航母入场";
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
let animId = null;
|
let animId = null;
|
||||||
let finished = false;
|
let finished = false;
|
||||||
@@ -497,7 +500,7 @@ const FujianEffect = (() => {
|
|||||||
drawBackdrop(ctx, w, h, progress);
|
drawBackdrop(ctx, w, h, progress);
|
||||||
drawWaves(ctx, waves, w, progress, carrierX, carrierY, scale);
|
drawWaves(ctx, waves, w, progress, carrierX, carrierY, scale);
|
||||||
drawCarrier(ctx, carrierX, carrierY, scale, progress);
|
drawCarrier(ctx, carrierX, carrierY, scale, progress);
|
||||||
drawHud(ctx, w, h, progress);
|
drawHud(ctx, w, h, progress, title);
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
animId = requestAnimationFrame(animate);
|
animId = requestAnimationFrame(animate);
|
||||||
|
|||||||
@@ -336,8 +336,9 @@ const J35Effect = (() => {
|
|||||||
* @param {number} w 画布宽度
|
* @param {number} w 画布宽度
|
||||||
* @param {number} h 画布高度
|
* @param {number} h 画布高度
|
||||||
* @param {number} progress 播放进度
|
* @param {number} progress 播放进度
|
||||||
|
* @param {string} title 入场标题
|
||||||
*/
|
*/
|
||||||
function drawHud(ctx, w, h, progress) {
|
function drawHud(ctx, w, h, progress, title) {
|
||||||
const enter = Math.min(1, Math.max(0, (progress - 0.13) / 0.18));
|
const enter = Math.min(1, Math.max(0, (progress - 0.13) / 0.18));
|
||||||
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.16));
|
const leave = Math.min(1, Math.max(0, (1 - progress) / 0.16));
|
||||||
const alpha = easeInOutSine(enter) * leave;
|
const alpha = easeInOutSine(enter) * leave;
|
||||||
@@ -351,7 +352,7 @@ const J35Effect = (() => {
|
|||||||
ctx.fillStyle = "rgba(2,6,23,0.62)";
|
ctx.fillStyle = "rgba(2,6,23,0.62)";
|
||||||
ctx.strokeStyle = "rgba(56,189,248,0.72)";
|
ctx.strokeStyle = "rgba(56,189,248,0.72)";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
roundRect(ctx, w * 0.5 - 230, y - 44, 460, 92, 18);
|
roundRect(ctx, w * 0.5 - 320, y - 46, 640, 96, 18);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
@@ -359,8 +360,8 @@ const J35Effect = (() => {
|
|||||||
ctx.font = "700 16px serif";
|
ctx.font = "700 16px serif";
|
||||||
ctx.fillText("STEALTH FIGHTER ARRIVAL", w * 0.5, y - 12);
|
ctx.fillText("STEALTH FIGHTER ARRIVAL", w * 0.5, y - 12);
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "900 42px serif";
|
ctx.font = "900 38px serif";
|
||||||
ctx.fillText("中国歼-35 破空入场", w * 0.5, y + 28);
|
ctx.fillText(title, w * 0.5, y + 28, 590);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,13 +394,15 @@ const J35Effect = (() => {
|
|||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
* @param {HTMLCanvasElement} canvas 全屏特效画布
|
||||||
* @param {Function} onEnd 结束回调
|
* @param {Function} onEnd 结束回调
|
||||||
|
* @param {object} options 特效附加参数
|
||||||
* @returns {{cancel: Function}}
|
* @returns {{cancel: Function}}
|
||||||
*/
|
*/
|
||||||
function start(canvas, onEnd) {
|
function start(canvas, onEnd, options = {}) {
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
const speedLines = createSpeedLines(w, h);
|
const speedLines = createSpeedLines(w, h);
|
||||||
|
const title = String(options.effect_title || "中国歼-35 破空入场").trim() || "中国歼-35 破空入场";
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
let animId = null;
|
let animId = null;
|
||||||
let finished = false;
|
let finished = false;
|
||||||
@@ -443,7 +446,7 @@ const J35Effect = (() => {
|
|||||||
drawSpeedLines(ctx, speedLines, w, progress);
|
drawSpeedLines(ctx, speedLines, w, progress);
|
||||||
drawSonicRing(ctx, w, h, progress);
|
drawSonicRing(ctx, w, h, progress);
|
||||||
drawJet(ctx, jetX, jetY, scale, progress);
|
drawJet(ctx, jetX, jetY, scale, progress);
|
||||||
drawHud(ctx, w, h, progress);
|
drawHud(ctx, w, h, progress, title);
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
animId = requestAnimationFrame(animate);
|
animId = requestAnimationFrame(animate);
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
'welcomeMessage' => $initialWelcomeMessage ?? null,
|
'welcomeMessage' => $initialWelcomeMessage ?? null,
|
||||||
'welcomeMessages' => $initialWelcomeMessages ?? [],
|
'welcomeMessages' => $initialWelcomeMessages ?? [],
|
||||||
'entryEffect' => $newbieEffect ?: ($initialRideEffect ?? ($initialPresenceTheme['presence_effect'] ?? ($weekEffect ?? null))),
|
'entryEffect' => $newbieEffect ?: ($initialRideEffect ?? ($initialPresenceTheme['presence_effect'] ?? ($weekEffect ?? null))),
|
||||||
|
'entryEffectOptions' => $newbieEffect ? null : ($initialRideEffectOptions ?? null),
|
||||||
'presenceTheme' => $initialPresenceTheme ?? null,
|
'presenceTheme' => $initialPresenceTheme ?? null,
|
||||||
'pendingProposal' => $pendingProposal ?? null,
|
'pendingProposal' => $pendingProposal ?? null,
|
||||||
'pendingDivorce' => $pendingDivorce ?? null,
|
'pendingDivorce' => $pendingDivorce ?? null,
|
||||||
|
|||||||
@@ -1132,8 +1132,14 @@ class ChatControllerTest extends TestCase
|
|||||||
$this->assertNotNull($rideMessage);
|
$this->assertNotNull($rideMessage);
|
||||||
$this->assertSame('座驾播报', $rideMessage['from_user']);
|
$this->assertSame('座驾播报', $rideMessage['from_user']);
|
||||||
$this->assertSame('j35', $rideMessage['ride_key']);
|
$this->assertSame('j35', $rideMessage['ride_key']);
|
||||||
|
$this->assertSame("{$user->username} 乘坐【歼-35测试座驾】闪亮登场", $rideMessage['effect_title']);
|
||||||
$this->assertStringContainsString($user->username, $rideMessage['content']);
|
$this->assertStringContainsString($user->username, $rideMessage['content']);
|
||||||
$this->assertSame('j35', $response->viewData('initialRideEffect'));
|
$this->assertSame('j35', $response->viewData('initialRideEffect'));
|
||||||
|
$this->assertSame([
|
||||||
|
'effect_title' => "{$user->username} 乘坐【歼-35测试座驾】闪亮登场",
|
||||||
|
'ride_name' => '歼-35测试座驾',
|
||||||
|
'operator' => $user->username,
|
||||||
|
], $response->viewData('initialRideEffectOptions'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user