mirror of
https://github.com/Cola-Echo/memory-manager-concurrent.git
synced 2026-06-14 08:15:50 +00:00
- isSummaryBook 函数新增对 Lore-char/lore-char 的检测 - 修复启用记忆搜索助手时进度条重复显示总结世界书任务的问题 - 更新错误提示信息,说明支持的命名规则 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
512 lines
17 KiB
JavaScript
512 lines
17 KiB
JavaScript
(function() {
|
|
|
|
/**
|
|
*
|
|
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
|
*
|
|
* Copyright (c) 2014 Martin Drapeau
|
|
* https://github.com/martindrapeau/backbone-game-engine
|
|
*
|
|
*/
|
|
|
|
var sequenceDelay = 300,
|
|
walkVelocity = 50,
|
|
fallAcceleration = 1200,
|
|
fallVelocity = 600;
|
|
|
|
var animations = {
|
|
"idle-left": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: 0,
|
|
scaleX: 1,
|
|
scaleY: 1
|
|
},
|
|
"idle-right": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: 0,
|
|
scaleX: -1,
|
|
scaleY: 1
|
|
},
|
|
"walk-left": {
|
|
sequences: [1, 0],
|
|
delay: sequenceDelay,
|
|
velocity: -walkVelocity,
|
|
scaleX: 1,
|
|
scaleY: 1
|
|
},
|
|
"walk-right": {
|
|
sequences: [1, 0],
|
|
delay: sequenceDelay,
|
|
velocity: walkVelocity,
|
|
scaleX: -1,
|
|
scaleY: 1
|
|
},
|
|
"fall-left": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: -walkVelocity,
|
|
yVelocity: fallVelocity,
|
|
yAcceleration: fallAcceleration,
|
|
scaleX: 1,
|
|
scaleY: 1
|
|
},
|
|
"fall-right": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: walkVelocity,
|
|
yVelocity: fallVelocity,
|
|
yAcceleration: fallAcceleration,
|
|
scaleX: -1,
|
|
scaleY: 1
|
|
},
|
|
"ko-left": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: -walkVelocity,
|
|
yVelocity: fallVelocity,
|
|
yAcceleration: fallAcceleration,
|
|
scaleX: 1,
|
|
scaleY: -1
|
|
},
|
|
"ko-right": {
|
|
sequences: [0],
|
|
delay: sequenceDelay,
|
|
velocity: walkVelocity,
|
|
yVelocity: fallVelocity,
|
|
yAcceleration: fallAcceleration,
|
|
scaleX: -1,
|
|
scaleY: -1
|
|
}
|
|
};
|
|
|
|
var hurtAnimation = {
|
|
sequences: [0],
|
|
delay: 300,
|
|
yVelocity: fallVelocity,
|
|
yAcceleration: fallAcceleration
|
|
};
|
|
animations["idle-hurt-left"] = _.extend({}, animations["idle-left"], hurtAnimation);
|
|
animations["idle-hurt-right"] = _.extend({}, animations["idle-right"], hurtAnimation);
|
|
animations["walk-hurt-left"] = _.extend({}, animations["walk-left"], hurtAnimation);
|
|
animations["walk-hurt-right"] = _.extend({}, animations["walk-right"], hurtAnimation);
|
|
animations["fall-hurt-left"] = _.extend({}, animations["fall-left"], hurtAnimation);
|
|
animations["fall-hurt-right"] = _.extend({}, animations["fall-right"], hurtAnimation);
|
|
|
|
|
|
Backbone.Character = Backbone.Sprite.extend({
|
|
defaults: _.extend({}, Backbone.Sprite.prototype.defaults, {
|
|
name: "character",
|
|
type: "character",
|
|
width: 32,
|
|
height: 32,
|
|
zIndex: 1,
|
|
spriteSheet: undefined,
|
|
state: "walk-left",
|
|
velocity: 0,
|
|
yVelocity: 0,
|
|
collision: true,
|
|
static: false,
|
|
visible: true,
|
|
health: 1,
|
|
attackDamage: 1,
|
|
floor: null,
|
|
ceiling: null,
|
|
aiDelay: 1000
|
|
}),
|
|
animations: animations,
|
|
initialize: function(attributes, options) {
|
|
Backbone.Sprite.prototype.initialize.apply(this, arguments);
|
|
options || (options = {});
|
|
|
|
this.on("attach", this.onAttach, this);
|
|
this.on("detach", this.onDetach, this);
|
|
|
|
this.on("hit", this.hit, this);
|
|
this.on("change:health", this.onHealthChange, this);
|
|
this.on("beforeFall", this.onBeforeFall, this);
|
|
},
|
|
onAttach: function() {
|
|
if (!this.engine) return;
|
|
this.onDetach();
|
|
},
|
|
onDetach: function() {
|
|
},
|
|
onBeforeFall: function() {
|
|
return this;
|
|
},
|
|
onHealthChange: function(model, health, options) {
|
|
options || (options = {});
|
|
var cur = this.getStateInfo(),
|
|
dir = options.dir || cur.dir,
|
|
opo = dir == "left" ? "right" : "left";
|
|
|
|
if (health == 0)
|
|
return this.knockout(options.sprite || null, dir, options.dir2 || null);
|
|
else if (health < this.previous("health"))
|
|
return this.hurt(options.sprite || null, dir, options.dir2 || null);
|
|
|
|
this.lastAIEvent = _.now();
|
|
|
|
return this;
|
|
},
|
|
knockout: function(sprite, dir) {
|
|
var opo = dir == "left" ? "right" : "left",
|
|
state = this.buildState("ko", opo);
|
|
this.whenAnimationEnds = null;
|
|
this.set({
|
|
state: state,
|
|
velocity: this.animations[state].velocity,
|
|
yVelocity: -this.animations[state].yVelocity,
|
|
sequenceIndex: 0,
|
|
collision: false
|
|
});
|
|
this.cancelUpdate = true;
|
|
return this;
|
|
},
|
|
hurt: function(sprite, dir) {
|
|
this.whenAnimationEnds = null;
|
|
this.set({
|
|
state: this.buildState("fall", "hurt", dir),
|
|
velocity: this.animations["ko-"+dir].velocity,
|
|
yVelocity: -this.animations["ko-"+dir].yVelocity,
|
|
sequenceIndex: 0
|
|
});
|
|
return this;
|
|
},
|
|
hit: function(sprite, dir, dir2) {
|
|
if (this._handlingSpriteHit) return this;
|
|
this._handlingSpriteHit = sprite;
|
|
|
|
var cur = this.getStateInfo();
|
|
|
|
if (cur.mov2 == "hurt") return this;
|
|
|
|
if (dir2 == "attack") {
|
|
this.cancelUpdate = true;
|
|
var attackDamage = sprite.get("attackDamage") || 1;
|
|
this.set({health: Math.max(this.get("health") - attackDamage, 0)}, {sprite: sprite, dir: dir, dir2: dir2});
|
|
} else if (cur.dir == dir && cur.mov2 == null) {
|
|
this.cancelUpdate = true;
|
|
this.set("state", this.buildState(cur.mov, cur.opo));
|
|
}
|
|
|
|
tis._handlingSpriteHit = undefined;
|
|
return this;
|
|
},
|
|
startNewAnimation: function(state, attrs, done) {
|
|
this.lastSequenceChangeTime = _.now();
|
|
this.set(_.extend({
|
|
state: state,
|
|
sequenceIndex: 0
|
|
}, attrs));
|
|
this.whenAnimationEnds = done;
|
|
return this;
|
|
},
|
|
updateSequenceIndex: function(dt) {
|
|
var sequenceIndex = this.get("sequenceIndex"),
|
|
animation = this.getAnimation(),
|
|
delay = animation.delay || 0,
|
|
now = _.now(),
|
|
triggerAnimationEnd = false;
|
|
|
|
if (!animation.sequences) {
|
|
sequenceIndex = 0;
|
|
this.lastSequenceChangeTime = now;
|
|
} else if (sequenceIndex >= animation.sequences.length) {
|
|
sequenceIndex = 0;
|
|
this.lastSequenceChangeTime = now;
|
|
triggerAnimationEnd = true;
|
|
} else if (delay && now > this.lastSequenceChangeTime + delay) {
|
|
sequenceIndex = sequenceIndex < animation.sequences.length-1 ? sequenceIndex + 1 : 0;
|
|
this.lastSequenceChangeTime = now;
|
|
if (sequenceIndex == 0) triggerAnimationEnd = true;
|
|
}
|
|
|
|
if (triggerAnimationEnd && typeof this.whenAnimationEnds == "function") {
|
|
this.whenAnimationEnds.call(this);
|
|
this.whenAnimationEnds = null;
|
|
}
|
|
|
|
return sequenceIndex;
|
|
},
|
|
ai: function(dt) {
|
|
return this;
|
|
},
|
|
update: function(dt) {
|
|
// Movements are only possible inside a world
|
|
if (!this.world) return true;
|
|
this.cancelUpdate = false;
|
|
|
|
// Velocity and state
|
|
var self = this,
|
|
velocity = this.get("velocity") || 0,
|
|
yVelocity = this.get("yVelocity") || 0,
|
|
x = this.get("x"),
|
|
y = this.get("y"),
|
|
state = this.get("state"),
|
|
cur = this.getStateInfo(),
|
|
animation = this.getAnimation(),
|
|
now = _.now(),
|
|
aiDelay = this.get("aiDelay"),
|
|
attrs = {};
|
|
|
|
// Handle AI
|
|
if (!this.lastAIEvent)
|
|
this.lastAIEvent = now;
|
|
else if (now > this.lastAIEvent + aiDelay) {
|
|
this.ai(now - this.lastAIEvent);
|
|
this.lastAIEvent = now;
|
|
if (this.cancelUpdate) return true;
|
|
}
|
|
|
|
if ((cur.mov == "ko" || cur.mov2 == "hurt") &&
|
|
this.get("sequenceIndex") == animation.sequences.length-1) {
|
|
// No sequence change - stay on last one
|
|
} else {
|
|
attrs.sequenceIndex = this.updateSequenceIndex();
|
|
}
|
|
|
|
if (velocity != animation.velocity) velocity = animation.velocity || 0;
|
|
|
|
if (cur.mov == "fall" || cur.mov == "ko" || cur.mov2 == "hurt") {
|
|
if (yVelocity < animation.yVelocity)
|
|
yVelocity += animation.yAcceleration * (dt/1000);
|
|
|
|
if (yVelocity >= animation.yVelocity)
|
|
yVelocity = animation.yVelocity;
|
|
attrs.yVelocity = yVelocity;
|
|
}
|
|
|
|
|
|
// Collision detection
|
|
var collision = this.get("collision"),
|
|
tileWidth = this.get("width"),
|
|
tileHeight = this.get("height"),
|
|
paddingLeft = this.get("paddingLeft"),
|
|
paddingRight = this.get("paddingRight"),
|
|
paddingBottom = this.get("paddingBottom"),
|
|
paddingTop = this.get("paddingTop"),
|
|
charWidth = tileWidth - paddingLeft - paddingRight,
|
|
charHeight = tileHeight - paddingTop - paddingBottom,
|
|
charLeftX = Math.round(x + velocity * (dt/1000)) + paddingLeft,
|
|
charRightX = charLeftX + charWidth,
|
|
bottomWorld = this.world.height() + tileHeight,
|
|
relativeVelocity = 0,
|
|
bottomY = _.minNotNull([
|
|
this.get("floor"),
|
|
bottomWorld
|
|
]);
|
|
|
|
var charBottomY, charTopY,
|
|
bottomPlatform, sprite, i, type;
|
|
function updateTopBottom() {
|
|
charBottomY = Math.round(y + yVelocity * (dt/1000)) + tileHeight - paddingBottom,
|
|
charTopY = charBottomY - charHeight,
|
|
self.buildCollisionMap(charTopY, charRightX, charBottomY, charLeftX);
|
|
if (collision)
|
|
self.world.findCollisions(self.collisionMap, null, self, true);
|
|
}
|
|
updateTopBottom();
|
|
|
|
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++) {
|
|
sprite = this.collisionMap.bottom.sprites[i];
|
|
type = sprite.get("type")
|
|
if (type == "tile" || type == "platform")
|
|
bottomY = Math.min(bottomY, sprite.getTop(true));
|
|
if (type == "platform") bottomPlatform = sprite;
|
|
}
|
|
|
|
if (yVelocity >= 0) {
|
|
// Walking or Falling...
|
|
if (charBottomY >= bottomY) {
|
|
if (charBottomY >= bottomWorld) {
|
|
this.world.remove(this);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++)
|
|
if (cur.mov != "ko")
|
|
this.collisionMap.bottom.sprites[i].trigger("hit", this, "top");
|
|
if (this.cancelUpdate) return this;
|
|
|
|
// Stop falling because obstacle below
|
|
attrs.yVelocity = yVelocity = 0;
|
|
attrs.y = y = bottomY - tileHeight + paddingBottom;
|
|
if (cur.mov == "fall")
|
|
attrs.state = this.buildState("walk", cur.mov2, cur.dir);
|
|
else if (cur.mov == "ko") {
|
|
attrs.velocity = velocity = 0;
|
|
}
|
|
updateTopBottom();
|
|
|
|
if (charBottomY == bottomY && bottomPlatform)
|
|
relativeVelocity = bottomPlatform.get("velocity");
|
|
|
|
} else if (cur.mov != "fall" && cur.mov != "ko" && charBottomY < bottomY) {
|
|
// Start falling if no obstacle below
|
|
attrs.state = this.buildState("fall", cur.mov2, cur.dir);
|
|
|
|
if (cur.mov == "walk" && velocity != 0) {
|
|
this.trigger("beforeFall");
|
|
if (this.cancelUpdate) return true;
|
|
}
|
|
}
|
|
|
|
} else if (yVelocity < 0) {
|
|
// Jumping
|
|
var topY = -400;
|
|
for (i = 0; i < this.collisionMap.top.sprites.length; i++) {
|
|
sprite = this.collisionMap.top.sprites[i];
|
|
if (sprite.get("type") == "tile")
|
|
topY = Math.max(topY, sprite.getBottom(true));
|
|
}
|
|
if (charTopY < topY) {
|
|
attrs.yVelocity = yVelocity = 0;
|
|
charTopY = topY;
|
|
charBottomY = topY + charHeight;
|
|
attrs.y = y = charBottomY - tileHeight;
|
|
updateTopBottom();
|
|
}
|
|
|
|
}
|
|
|
|
// When not in play mode, do not allow horizontal displacements or animations
|
|
if (this.world.get("state") != "play") {
|
|
attrs.velocity = velocity = 0;
|
|
attrs.sequenceIndex = this.get("sequenceIndex");
|
|
|
|
} else {
|
|
|
|
// Walls and other obstacles
|
|
if (velocity <= 0 && collision) {
|
|
// Turn around if obstacle left
|
|
var worldLeft = -tileWidth,
|
|
leftX = worldLeft,
|
|
leftCharacter;
|
|
if (cur.mov != "ko" && cur.mov != "idle")
|
|
for (i = 0; i < this.collisionMap.left.sprites.length; i++) {
|
|
sprite = this.collisionMap.left.sprites[i];
|
|
leftX = Math.max(leftX, sprite.getRight(true));
|
|
if (sprite.get("type") == "character" &&
|
|
(!leftCharacter || sprite.getRight(true) > leftCharacter.getRight(true)))
|
|
leftCharacter = sprite;
|
|
}
|
|
|
|
if (charLeftX <= leftX) {
|
|
if (charLeftX <= worldLeft) {
|
|
this.world.remove(this);
|
|
return false;
|
|
}
|
|
if (leftCharacter && cur.mov2 != "hurt") {
|
|
leftCharacter.trigger("hit", this, "right", cur.mov2);
|
|
if (this.cancelUpdate) return true;
|
|
}
|
|
attrs.velocity = velocity = velocity * -1;
|
|
attrs.state = this.buildState(cur.mov, cur.mov2, cur.opo);
|
|
attrs.x = x = leftX - paddingLeft;
|
|
}
|
|
}
|
|
|
|
if (velocity >= 0 && collision) {
|
|
// Turn around if obstacle to the right
|
|
var worldRight = this.world.width(),
|
|
rightX = worldRight,
|
|
rightCharacter;
|
|
if (cur.mov != "ko" && cur.mov != "idle")
|
|
for (i = 0; i < this.collisionMap.right.sprites.length; i++) {
|
|
sprite = this.collisionMap.right.sprites[i];
|
|
rightX = Math.min(rightX, sprite.getLeft(true));
|
|
if (sprite.get("type") == "character" &&
|
|
(!rightCharacter || sprite.getLeft(true) < rightCharacter.getLeft(true)))
|
|
rightCharacter = sprite;
|
|
}
|
|
|
|
if (charRightX >= rightX) {
|
|
if (charRightX >= worldRight) {
|
|
this.world.remove(this);
|
|
return false;
|
|
}
|
|
if (rightCharacter && cur.mov2 != "hurt") {
|
|
rightCharacter.trigger("hit", this, "left", cur.mov2);
|
|
if (this.cancelUpdate) return true;
|
|
}
|
|
attrs.velocity = velocity = velocity * -1;
|
|
attrs.state = this.buildState(cur.mov, cur.mov2, cur.opo);
|
|
attrs.x = x = rightX - charWidth - paddingLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (velocity || relativeVelocity) attrs.x = x = x + Math.round((velocity + relativeVelocity) * (dt/1000));
|
|
if (yVelocity) attrs.y = y = y + Math.round(yVelocity * (dt/1000));
|
|
|
|
// Set modified attributes
|
|
if (!_.isEmpty(attrs)) this.set(attrs);
|
|
|
|
if (typeof this.onUpdate == "function") return this.onUpdate(dt);
|
|
return true;
|
|
},
|
|
toggleDirection: function(dirIntent) {
|
|
var cur = this.getStateInfo();
|
|
this.set({state: this.buildState(cur.mov, cur.mov2, dirIntent)});
|
|
return this;
|
|
},
|
|
getStateInfo: function(state) {
|
|
var state = state || this.get("state"),
|
|
pieces = state.split("-");
|
|
if (pieces.length < 2) return {
|
|
state: state,
|
|
mov: state
|
|
};
|
|
|
|
var stateInfo = {};
|
|
stateInfo.mov = pieces[0];
|
|
stateInfo.mov2 = pieces.length == 3 ? pieces[1] : null;
|
|
stateInfo.dir = pieces.length == 3 ? pieces[2] : pieces[1];
|
|
stateInfo.opo = stateInfo.dir == "right" ? "left" : "right";
|
|
return stateInfo;
|
|
},
|
|
isAttacking: function() {
|
|
if (this.cancelUpdate) return false;
|
|
var cur = this.getStateInfo();
|
|
return cur.mov2 == "attack";
|
|
},
|
|
buildState: function(piece1, piece2, piece3) {
|
|
var state = "";
|
|
if (piece1) state += piece1;
|
|
if (piece2) state += (state.length ? "-" : "") + piece2;
|
|
if (piece3) state += (state.length ? "-" : "") + piece3;
|
|
return state;
|
|
},
|
|
buildCollisionMap: function(top, right, bottom, left) {
|
|
this.collisionMap || (this.collisionMap = {
|
|
right: {x: 0, y: 0, dir: "right", sprites: [], sprite: null},
|
|
left: {x: 0, y: 0, dir: "left", sprites: [], sprite: null},
|
|
bottom: {x: 0, y: 0, dir: "bottom", sprites: [], sprite: null},
|
|
top: {x: 0, y: 0, dir: "top", sprites: [], sprite: null}
|
|
});
|
|
|
|
var width = right - left,
|
|
height = bottom - top;
|
|
this.collisionMap.left.x = left;
|
|
this.collisionMap.right.x = right;
|
|
this.collisionMap.left.y = this.collisionMap.right.y = top + height*0.20;
|
|
this.collisionMap.top.x = this.collisionMap.bottom.x = left + width*0.20;
|
|
this.collisionMap.top.y = top;
|
|
this.collisionMap.bottom.y = bottom;
|
|
this.collisionMap.left.height = this.collisionMap.right.height = height*0.60;
|
|
this.collisionMap.left.width = this.collisionMap.right.width = 0;
|
|
this.collisionMap.top.width = this.collisionMap.bottom.width = width*0.60;
|
|
this.collisionMap.top.height = this.collisionMap.bottom.height = 0;
|
|
|
|
for (var m in this.collisionMap)
|
|
if (this.collisionMap.hasOwnProperty(m)) {
|
|
this.collisionMap[m].sprites.length = 0;
|
|
this.collisionMap[m].sprite = null;
|
|
}
|
|
}
|
|
});
|
|
|
|
}).call(this); |