From 85c94a9e5dc94c3cf93955d87fb2e84f7f2fe16c Mon Sep 17 00:00:00 2001 From: genxium Date: Fri, 23 Sep 2022 16:42:44 +0800 Subject: [PATCH 1/7] Preparation for changing collision system. --- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scripts/BasePlayer.js | 36 ------ frontend/assets/scripts/Login.js | 1 + frontend/assets/scripts/Map.js | 16 +-- .../scripts/TileCollisionManagerSingleton.js | 113 ------------------ frontend/assets/scripts/modules/Collisions.js | 1 + .../assets/scripts/modules/Collisions.js.meta | 9 ++ frontend/settings/project.json | 19 +-- 8 files changed, 28 insertions(+), 169 deletions(-) create mode 100644 frontend/assets/scripts/modules/Collisions.js create mode 100644 frontend/assets/scripts/modules/Collisions.js.meta diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 34a6ab6..c6d4630 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 209.7912853806815, + 216.05530045313827, 0, 0, 0, diff --git a/frontend/assets/scripts/BasePlayer.js b/frontend/assets/scripts/BasePlayer.js index e4ce7f0..6aa7c3d 100644 --- a/frontend/assets/scripts/BasePlayer.js +++ b/frontend/assets/scripts/BasePlayer.js @@ -37,7 +37,6 @@ module.export = cc.Class({ const self = this; self.contactedControlledPlayers = []; self.contactedNPCPlayers = []; - self.coveringShelterZReducers = []; self.computedNewDifferentPosLocalToParentWithinCurrentFrame = null; self.actionMangerSingleton = new cc.ActionManager(); @@ -93,25 +92,6 @@ module.export = cc.Class({ } }, - _addCoveringShelterZReducer(comp) { - const self = this; - for (let coveringShelterZReducer of self.coveringShelterZReducers) { - if (coveringShelterZReducer._id == comp._id) { - return false; - } - } - self.coveringShelterZReducers.push(comp); - return true; - }, - - _removeCoveringShelterZReducer(comp) { - const self = this; - self.coveringShelterZReducers = self.coveringShelterZReducers.filter((coveringShelterZReducer) => { - return coveringShelterZReducer._id != comp._id; - }); - return true; - }, - _addContactedBarrier(collider) { const self = this; if (!self.contactedBarriers) { @@ -379,14 +359,6 @@ module.export = cc.Class({ case "PolygonBoundaryBarrier": playerScriptIns._addContactedBarrier(other); break; - case "PolygonBoundaryShelter": - break; - case "PolygonBoundaryShelterZReducer": - playerScriptIns._addCoveringShelterZReducer(other); - if (1 == playerScriptIns.coveringShelterZReducers.length) { - setLocalZOrder(self.node, 2); - } - break; default: break; } @@ -406,14 +378,6 @@ module.export = cc.Class({ case "PolygonBoundaryBarrier": playerScriptIns._removeContactedBarrier(other); break; - case "PolygonBoundaryShelter": - break; - case "PolygonBoundaryShelterZReducer": - playerScriptIns._removeCoveringShelterZReducer(other); - if (0 == playerScriptIns.coveringShelterZReducers.length) { - setLocalZOrder(self.node, 5); - } - break; default: break; } diff --git a/frontend/assets/scripts/Login.js b/frontend/assets/scripts/Login.js index cd46436..c8b2b77 100644 --- a/frontend/assets/scripts/Login.js +++ b/frontend/assets/scripts/Login.js @@ -1,5 +1,6 @@ const i18n = require('LanguageData'); i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field + cc.Class({ extends: cc.Component, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 5fb8b60..245ab8a 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -1,6 +1,8 @@ const i18n = require('LanguageData'); i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field +const collisions = require('./modules/Collisions'); + window.ALL_MAP_STATES = { VISUAL: 0, // For free dragging & zooming. EDITING_BELONGING: 1, @@ -56,14 +58,6 @@ cc.Class({ type: cc.Prefab, default: null, }, - polygonBoundaryShelterPrefab: { - type: cc.Prefab, - default: null, - }, - polygonBoundaryShelterZReducerPrefab: { - type: cc.Prefab, - default: null, - }, keyboardInputControllerNode: { type: cc.Node, default: null @@ -354,6 +348,9 @@ cc.Class({ self.recentInputCacheMaxCount = 1024; self.toRollbackRenderFrameId1 = null; self.toRollbackInputFrameId1 = null; + + self.latestCollisionSys = new collisions.Collisions(); + self.chaserCollisionSys = new collisions.Collisions(); self.transitToState(ALL_MAP_STATES.VISUAL); @@ -386,8 +383,7 @@ cc.Class({ const mapNode = self.node; const canvasNode = mapNode.parent; - cc.director.getCollisionManager().enabled = true; - cc.director.getCollisionManager().enabledDebugDraw = CC_DEBUG; + cc.director.getCollisionManager().enabled = false; // self.musicEffectManagerScriptIns = self.node.getComponent("MusicEffectManager"); self.musicEffectManagerScriptIns = null; diff --git a/frontend/assets/scripts/TileCollisionManagerSingleton.js b/frontend/assets/scripts/TileCollisionManagerSingleton.js index 1d710b0..2fdee4d 100644 --- a/frontend/assets/scripts/TileCollisionManagerSingleton.js +++ b/frontend/assets/scripts/TileCollisionManagerSingleton.js @@ -334,10 +334,6 @@ window.battleEntityTypeNameToGlobalGid = {}; TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNode) { let toRet = { barriers: [], - shelters: [], - shelterChainTails: [], - shelterChainHeads: [], - sheltersZReducer: [], frameAnimations: [], grandBoundaries: [], }; @@ -404,8 +400,6 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo var currentObjectGroupUnderTile = mapInfo._parseObjectGroup(ch); gidBoundariesMap[parentGid] = { barriers: [], - shelters: [], - sheltersZReducer: [], }; for (let oidx = 0; oidx < currentObjectGroupUnderTile._objects.length; ++oidx) { const oo = currentObjectGroupUnderTile._objects[oidx]; @@ -429,22 +423,6 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo brToPushTmp.boundaryType = boundaryType; gidBoundariesMap[parentGid].barriers.push(brToPushTmp); break; - case "shelter": - let shToPushTmp = []; - for (let shidx = 0; shidx < polylinePoints.length; ++shidx) { - shToPushTmp.push(cc.v2(oo.x, oo.y).add(polylinePoints[shidx])); - } - shToPushTmp.boundaryType = boundaryType; - gidBoundariesMap[parentGid].shelters.push(shToPushTmp); - break; - case "shelter_z_reducer": - let shzrToPushTmp = []; - for (let shzridx = 0; shzridx < polylinePoints.length; ++shzridx) { - shzrToPushTmp.push(cc.v2(oo.x, oo.y).add(polylinePoints[shzridx])); - } - shzrToPushTmp.boundaryType = boundaryType; - gidBoundariesMap[parentGid].sheltersZReducer.push(shzrToPushTmp); - break; default: break; } @@ -510,22 +488,6 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo toPushBarriers.boundaryType = boundaryType; toRet.barriers.push(toPushBarriers); break; - case "shelter": - let toPushShelters = []; - for (let kk = 0; kk < polylinePoints.length; ++kk) { - toPushShelters.push(this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset.add(polylinePoints[kk]))); - } - toPushShelters.boundaryType = boundaryType; - toRet.shelters.push(toPushShelters); - break; - case "shelter_z_reducer": - let toPushSheltersZReducer = []; - for (let kkk = 0; kkk < polylinePoints.length; ++kkk) { - toPushSheltersZReducer.push(this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset.add(polylinePoints[kkk]))); - } - toPushSheltersZReducer.boundaryType = boundaryType; - toRet.sheltersZReducer.push(toPushSheltersZReducer); - break; default: break; } @@ -585,23 +547,6 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo } toRet.barriers.push(brToPushTmp); } - for (let shidx = 0; shidx < gidBoundaries.shelters.length; ++shzridx) { - const theShelter = gidBoundaries.shelters[shidx]; // An array of cc.v2 points. - let shToPushTmp = []; - for (let tshidx = 0; tshidx < theShelter.length; ++tshidx) { - shToPushTmp.push(topLeftOfWholeTsxTileInMapNode.add(cc.v2(theShelter[tshidx].x, -theShelter[tshidx].y))); - } - toRet.shelters.push(shToPushTmp); - } - for (let shzridx = 0; shzridx < gidBoundaries.sheltersZReducer.length; ++shzridx) { - const theShelter = gidBoundaries.sheltersZReducer[shzridx]; // An array of cc.v2 points. - let shzrToPushTmp = []; - for (let tshzridx = 0; tshzridx < theShelter.length; ++tshzridx) { - shzrToPushTmp.push(topLeftOfWholeTsxTileInMapNode.add(cc.v2(theShelter[tshzridx].x, -theShelter[tshzridx].y))); - } - toRet.sheltersZReducer.push(shzrToPushTmp); - } - continue; default: @@ -683,47 +628,6 @@ TileCollisionManager.prototype.initMapNodeByTiledBoundaries = function(mapScript frameAnimInType.push(animNode); } - for (let boundaryObj of extractedBoundaryObjs.shelterChainTails) { - const newShelter = cc.instantiate(mapScriptIns.polygonBoundaryShelterPrefab); - const newBoundaryOffsetInMapNode = cc.v2(boundaryObj[0].x, boundaryObj[0].y); - newShelter.setPosition(newBoundaryOffsetInMapNode); - newShelter.setAnchorPoint(cc.v2(0, 0)); - const newShelterColliderIns = newShelter.getComponent(cc.PolygonCollider); - newShelterColliderIns.points = []; - for (let p of boundaryObj) { - newShelterColliderIns.points.push(p.sub(newBoundaryOffsetInMapNode)); - } - newShelter.pTiledLayer = boundaryObj.pTiledLayer; - newShelter.tileDiscretePos = boundaryObj.tileDiscretePos; - if (null != boundaryObj.imageObject) { - newShelter.imageObject = boundaryObj.imageObject; - newShelter.tailOrHead = "tail"; - window.addToGlobalShelterChainVerticeMap(newShelter.imageObject.imageObjectNode); // Deliberately NOT adding at the "traversal of shelterChainHeads". - } - newShelter.boundaryObj = boundaryObj; - mapScriptIns.node.addChild(newShelter); - } - - for (let boundaryObj of extractedBoundaryObjs.shelterChainHeads) { - const newShelter = cc.instantiate(mapScriptIns.polygonBoundaryShelterPrefab); - const newBoundaryOffsetInMapNode = cc.v2(boundaryObj[0].x, boundaryObj[0].y); - newShelter.setPosition(newBoundaryOffsetInMapNode); - newShelter.setAnchorPoint(cc.v2(0, 0)); - const newShelterColliderIns = newShelter.getComponent(cc.PolygonCollider); - newShelterColliderIns.points = []; - for (let p of boundaryObj) { - newShelterColliderIns.points.push(p.sub(newBoundaryOffsetInMapNode)); - } - newShelter.pTiledLayer = boundaryObj.pTiledLayer; - newShelter.tileDiscretePos = boundaryObj.tileDiscretePos; - if (null != boundaryObj.imageObject) { - newShelter.imageObject = boundaryObj.imageObject; - newShelter.tailOrHead = "head"; - } - newShelter.boundaryObj = boundaryObj; - mapScriptIns.node.addChild(newShelter); - } - mapScriptIns.barrierColliders = []; for (let boundaryObj of extractedBoundaryObjs.barriers) { const newBarrier = cc.instantiate(mapScriptIns.polygonBoundaryBarrierPrefab); @@ -739,19 +643,6 @@ TileCollisionManager.prototype.initMapNodeByTiledBoundaries = function(mapScript mapScriptIns.node.addChild(newBarrier); } - for (let boundaryObj of extractedBoundaryObjs.sheltersZReducer) { - const newShelter = cc.instantiate(mapScriptIns.polygonBoundaryShelterZReducerPrefab); - const newBoundaryOffsetInMapNode = cc.v2(boundaryObj[0].x, boundaryObj[0].y); - newShelter.setPosition(newBoundaryOffsetInMapNode); - newShelter.setAnchorPoint(cc.v2(0, 0)); - const newShelterColliderIns = newShelter.getComponent(cc.PolygonCollider); - newShelterColliderIns.points = []; - for (let p of boundaryObj) { - newShelterColliderIns.points.push(p.sub(newBoundaryOffsetInMapNode)); - } - mapScriptIns.node.addChild(newShelter); - } - const allLayers = tiledMapIns.getLayers(); for (let layer of allLayers) { const layerType = layer.getProperty("type"); @@ -759,10 +650,6 @@ TileCollisionManager.prototype.initMapNodeByTiledBoundaries = function(mapScript case "barrier_and_shelter": setLocalZOrder(layer.node, 3); break; - case "shelter_preview": - layer.node.opacity = 100; - setLocalZOrder(layer.node, 500); - break; default: break; } diff --git a/frontend/assets/scripts/modules/Collisions.js b/frontend/assets/scripts/modules/Collisions.js new file mode 100644 index 0000000..a53753d --- /dev/null +++ b/frontend/assets/scripts/modules/Collisions.js @@ -0,0 +1 @@ +!function(_){var t={};function s(n){if(t[n])return t[n].exports;var e=t[n]={i:n,l:!1,exports:{}};return _[n].call(e.exports,e,e.exports,s),e.l=!0,e.exports}s.m=_,s.c=t,s.d=function(_,t,n){s.o(_,t)||Object.defineProperty(_,t,{enumerable:!0,get:n})},s.r=function(_){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(_,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(_,"__esModule",{value:!0})},s.t=function(_,t){if(1&t&&(_=s(_)),8&t)return _;if(4&t&&"object"==typeof _&&_&&_.__esModule)return _;var n=Object.create(null);if(s.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:_}),2&t&&"string"!=typeof _)for(var e in _)s.d(n,e,function(t){return _[t]}.bind(null,e));return n},s.n=function(_){var t=_&&_.__esModule?function(){return _.default}:function(){return _};return s.d(t,"a",t),t},s.o=function(_,t){return Object.prototype.hasOwnProperty.call(_,t)},s.p="",s(s.s=0)}([function(_,t,s){"use strict";s.r(t);const n=[];class e{constructor(){this._bvh_parent=null,this._bvh_branch=!0,this._bvh_left=null,this._bvh_right=null,this._bvh_sort=0,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}static getBranch(){return n.length?n.pop():new e}static releaseBranch(_){n.push(_)}static sortBranches(_,t){return _.sort>t.sort?-1:1}}class i{constructor(){this._hierarchy=null,this._bodies=[],this._dirty_branches=[]}insert(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=this,this._bodies.push(_)}const s=_._polygon,n=_.x,i=_.y;s&&(_._dirty_coords||_.x!==_._x||_.y!==_._y||_.angle!==_._angle||_.scale_x!==_._scale_x||_.scale_y!==_._scale_y)&&_._calculateCoords();const r=_._bvh_padding,h=s?0:_.radius*_.scale,o=(s?_._min_x:n-h)-r,l=(s?_._min_y:i-h)-r,a=(s?_._max_x:n+h)+r,c=(s?_._max_y:i+h)+r;_._bvh_min_x=o,_._bvh_min_y=l,_._bvh_max_x=a,_._bvh_max_y=c;let b=this._hierarchy,v=0;if(b)for(;;){if(!b._bvh_branch){const t=b._bvh_parent,s=b._bvh_min_x,n=b._bvh_min_y,i=b._bvh_max_x,r=b._bvh_max_y,h=b._bvh_parent=_._bvh_parent=e.getBranch();h._bvh_parent=t,h._bvh_left=b,h._bvh_right=_,h._bvh_sort=v++,h._bvh_min_x=oi?a:i,h._bvh_max_y=c>r?c:r,t?t._bvh_left===b?t._bvh_left=h:t._bvh_right=h:this._hierarchy=h;break}{const _=b._bvh_left,t=_._bvh_min_y,s=_._bvh_max_x,n=_._bvh_max_y,e=o<_._bvh_min_x?o:_._bvh_min_x,i=ls?a:s,h=c>n?c:n,u=(r-e)*(h-i)-(s-_._bvh_min_x)*(n-t),y=b._bvh_right,x=y._bvh_min_x,m=y._bvh_min_y,d=y._bvh_max_x,f=y._bvh_max_y,p=od?a:d,P=c>f?c:f,M=(w-p)*(P-g)-(d-x)*(f-m);b._bvh_sort=v++,b._bvh_min_x=ew?r:w,b._bvh_max_y=h>P?h:P,b=u<=M?_:y}}else this._hierarchy=_}remove(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=null,this._bodies.splice(this._bodies.indexOf(_),1)}if(this._hierarchy===_)return void(this._hierarchy=null);const s=_._bvh_parent,n=s._bvh_parent,i=s._bvh_left,r=i===_?s._bvh_right:i;if(r._bvh_parent=n,r._bvh_branch&&(r._bvh_sort=s._bvh_sort),n){n._bvh_left===s?n._bvh_left=r:n._bvh_right=r;let _=n;for(;_;){const t=_._bvh_left,s=t._bvh_min_x,n=t._bvh_min_y,e=t._bvh_max_x,i=t._bvh_max_y,r=_._bvh_right,h=r._bvh_min_x,o=r._bvh_min_y,l=r._bvh_max_x,a=r._bvh_max_y;_._bvh_min_x=sl?e:l,_._bvh_max_y=i>a?i:a,_=_._bvh_parent}}else this._hierarchy=r;e.releaseBranch(s)}update(){const _=this._bodies,t=_.length;for(let s=0;st._bvh_max_x||l>t._bvh_max_y}n&&(this.remove(t,!0),this.insert(t,!0))}}potentials(_){const t=[],s=_._bvh_min_x,n=_._bvh_min_y,e=_._bvh_max_x,i=_._bvh_max_y;let r=this._hierarchy,h=!0;if(!r||!r._bvh_branch)return t;for(;r;){if(h){h=!1;let _=r._bvh_branch?r._bvh_left:null;for(;_&&_._bvh_max_x>=s&&_._bvh_max_y>=n&&_._bvh_min_x<=e&&_._bvh_min_y<=i;)r=_,_=r._bvh_branch?r._bvh_left:null}const o=r._bvh_branch,l=o?r._bvh_right:null;if(l&&l._bvh_max_x>s&&l._bvh_max_y>n&&l._bvh_min_xu&&l>y}(_,t)||(e&&_._dirty_normals&&_._calculateNormals(),i&&t._dirty_normals&&t._calculateNormals(),r=e&&i?function(_,t,s=null){const n=_._coords.length,e=t._coords.length;if(2===n&&2===e){const n=_._coords,e=t._coords;return s&&(s.overlap=0),n[0]===e[0]&&n[1]===e[1]}const i=_._coords,r=t._coords,h=_._normals,o=t._normals;if(n>2)for(let _=0,t=1;_2)for(let _=0,t=1;_h*h)return!1;if(s){const _=Math.sqrt(o);s.a_in_b=n<=e&&_<=e-n,s.b_in_a=e<=n&&_<=n-e,s.overlap=h-_,s.overlap_x=i/_,s.overlap_y=r/_}return!0}(_,t,s)),s&&(s.collision=r),r}function o(_,t,s=null,n=!1){const e=_._coords,i=_._edges,r=_._normals,h=t.x,o=t.y,l=t.radius*t.scale,a=2*l,c=l*l,b=e.length;let v=!0,u=!0,y=null,x=0,m=0;if(2===b){const _=h-e[0],t=o-e[1],n=_*_+t*t;if(n>c)return!1;if(s){const s=Math.sqrt(n);y=l-s,x=_/s,m=t/s,u=!1}}else for(let _=0,t=1;_f*f+p*p?1:0;let P=!1,M=0,T=0,B=0;if(s&&v&&n*n+d*d>c&&(v=!1),w){const t=-1===w,r=t?0===_?b-2:_-2:_===b-2?0:_+2,a=r+1,v=h-e[r],y=o-e[a],x=i[r],m=i[a],f=v*x+y*m;if((f<0?-1:f>x*x+m*m?1:0)===-w){const _=t?n:v,e=t?d:y,i=_*_+e*e;if(i>c)return!1;if(s){const t=Math.sqrt(i);P=!0,M=l-t,T=_/t,B=e/t,u=!1}}}else{const e=r[_],i=r[t],h=n*e+d*i,o=h<0?-h:h;if(h>0&&o>l)return!1;s&&(P=!0,M=l-h,T=e,B=i,(u&&h>=0||MM)&&(y=M,x=T,m=B)}return s&&(s.a_in_b=n?u:v,s.b_in_a=n?v:u,s.overlap=y,s.overlap_x=n?-x:x,s.overlap_y=n?-m:m),!0}function l(_,t,s,n,e=null){const i=_.length,r=t.length;if(!i||!r)return!0;let h=null,o=null,l=null,a=null;for(let t=0,e=1;ti)&&(h=i),(null===o||oi)&&(l=i),(null===a||aa||oa)_=h-a,e.a_in_b=!1;else{const t=o-l,s=a-h;_=ti){const t=_<0?-1:1;e.overlap=i,e.overlap_x=s*t,e.overlap_y=n*t}}return!1}class a{constructor(_=0,t=0,s=0){this.x=_,this.y=t,this.padding=s,this._circle=!1,this._polygon=!1,this._point=!1,this._bvh=null,this._bvh_parent=null,this._bvh_branch=!1,this._bvh_padding=s,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}collides(_,t=null,s=!0){return h(this,_,t,s)}potentials(){const _=this._bvh;if(null===_)throw new Error("Body does not belong to a collision system");return _.potentials(this)}remove(){const _=this._bvh;_&&_.remove(this,!1)}createResult(){return new r}static createResult(){return new r}}class c extends a{constructor(_=0,t=0,s=0,n=1,e=0){super(_,t,e),this.radius=s,this.scale=n}draw(_){const t=this.x,s=this.y,n=this.radius*this.scale;_.moveTo(t+n,s),_.arc(t,s,n,0,2*Math.PI)}}class b extends a{constructor(_=0,t=0,s=[],n=0,e=1,i=1,r=0){super(_,t,r),this.angle=n,this.scale_x=e,this.scale_y=i,this._polygon=!0,this._x=_,this._y=t,this._angle=n,this._scale_x=e,this._scale_y=i,this._min_x=0,this._min_y=0,this._max_x=0,this._max_y=0,this._points=null,this._coords=null,this._edges=null,this._normals=null,this._dirty_coords=!0,this._dirty_normals=!0,b.prototype.setPoints.call(this,s)}draw(_){(this._dirty_coords||this.x!==this._x||this.y!==this._y||this.angle!==this._angle||this.scale_x!==this._scale_x||this.scale_y!==this._scale_y)&&this._calculateCoords();const t=this._coords;if(2===t.length)_.moveTo(t[0],t[1]),_.arc(t[0],t[1],1,0,2*Math.PI);else{_.moveTo(t[0],t[1]);for(let s=2;s4&&_.lineTo(t[0],t[1])}}setPoints(_){const t=_.length;this._points=new Float64Array(2*t),this._coords=new Float64Array(2*t),this._edges=new Float64Array(2*t),this._normals=new Float64Array(2*t);const s=this._points;for(let n=0,e=0,i=1;nl&&(l=h),uc&&(c=u))}this._x=_,this._y=t,this._angle=s,this._scale_x=n,this._scale_y=e,this._min_x=o,this._min_y=a,this._max_x=l,this._max_y=c,this._dirty_coords=!1,this._dirty_normals=!0}_calculateNormals(){const _=this._coords,t=this._edges,s=this._normals,n=_.length;for(let e=0,i=1;e Date: Sat, 24 Sep 2022 12:01:50 +0800 Subject: [PATCH 2/7] A temp commit after coping with many obvious runtime errors. --- ConcerningEdgeCases.md | 11 + battle_srv/models/ringbuf.go | 4 +- battle_srv/models/room.go | 58 +- battle_srv/models/room_heap_manager.go | 34 +- .../pb_output/room_downsync_frame.pb.go | 293 ++- .../pbfiles/room_downsync_frame.proto | 5 +- frontend/assets/scenes/login.fire | 3 - frontend/assets/scenes/wechatGameLogin.fire | 1706 ----------------- .../assets/scenes/wechatGameLogin.fire.meta | 7 - frontend/assets/scripts/BasePlayer.js | 291 --- frontend/assets/scripts/FindingPlayer.js | 19 +- frontend/assets/scripts/Login.js | 194 +- frontend/assets/scripts/Map.js | 486 ++--- frontend/assets/scripts/RingBuffer.js | 63 + ...atGameLogin.js.meta => RingBuffer.js.meta} | 2 +- .../scripts/TileCollisionManagerSingleton.js | 5 +- frontend/assets/scripts/WechatGameLogin.js | 628 ------ frontend/assets/scripts/WsSessionMgr.js | 30 +- ...om_downsync_frame_proto_bundle.forcemsg.js | 74 +- 19 files changed, 546 insertions(+), 3367 deletions(-) create mode 100644 ConcerningEdgeCases.md delete mode 100644 frontend/assets/scenes/wechatGameLogin.fire delete mode 100644 frontend/assets/scenes/wechatGameLogin.fire.meta create mode 100644 frontend/assets/scripts/RingBuffer.js rename frontend/assets/scripts/{WechatGameLogin.js.meta => RingBuffer.js.meta} (74%) delete mode 100644 frontend/assets/scripts/WechatGameLogin.js diff --git a/ConcerningEdgeCases.md b/ConcerningEdgeCases.md new file mode 100644 index 0000000..825888f --- /dev/null +++ b/ConcerningEdgeCases.md @@ -0,0 +1,11 @@ +Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment +- player#1: renderFrameId = 100, significantly lagged due to local CPU being overheated +- player#2: renderFrameId = 240 +- player#3: renderFrameId = 239 +- player#4: renderFrameId = 242 + +players #2, #3 #4 would receive "outdated(in their subjective feelings)" from then on, and be forced to rollback many frames. + +In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. On the frontend we could only mitigate the impact to players #2, #3, #4 by certain buffering mechanisms. + +However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames. diff --git a/battle_srv/models/ringbuf.go b/battle_srv/models/ringbuf.go index 5d2d82d..572e4d1 100644 --- a/battle_srv/models/ringbuf.go +++ b/battle_srv/models/ringbuf.go @@ -1,8 +1,8 @@ package models type RingBuffer struct { - Ed int32 // write index - St int32 // read index + Ed int32 // write index, open index + St int32 // read index, closed index EdFrameId int32 StFrameId int32 N int32 diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index ebf3055..f4e5e7c 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -3,7 +3,6 @@ package models import ( "encoding/xml" "fmt" - "strings" "github.com/ByteArena/box2d" "github.com/golang/protobuf/proto" "github.com/gorilla/websocket" @@ -15,6 +14,7 @@ import ( . "server/common" "server/common/utils" pb "server/pb_output" + "strings" "sync" "sync/atomic" "time" @@ -31,8 +31,8 @@ const ( ) const ( - MAGIC_REMOVED_AT_FRAME_ID_PERMANENT_REMOVAL_MARGIN = 5 - MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START = -99 + MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START = -1 + MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START = 0 MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_ADDED_AND_ACKED = -98 MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_READDED_AND_ACKED = -97 @@ -535,7 +535,7 @@ func (pR *Room) ChooseStage() error { ErrFatal(err) rand.Seed(time.Now().Unix()) - stageNameList := []string{/*"pacman" ,*/ "richsoil"} + stageNameList := []string{ /*"pacman" ,*/ "richsoil"} chosenStageIndex := rand.Int() % len(stageNameList) // Hardcoded temporarily. -- YFLu pR.StageName = stageNameList[chosenStageIndex] @@ -701,10 +701,10 @@ func (pR *Room) CanPopSt(refLowerInputFrameId int32) bool { func (pR *Room) AllPlayerInputsBufferString() string { s := make([]string, 0) - s = append(s, fmt.Sprintf("{lastAllConfirmedInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v}", pR.LastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameIdWithChange)) + s = append(s, fmt.Sprintf("{lastAllConfirmedInputFrameId: %v, lastAllConfirmedInputFrameIdWithChange: %v}", pR.LastAllConfirmedInputFrameId, pR.LastAllConfirmedInputFrameIdWithChange)) for playerId, player := range pR.Players { - s = append(s, fmt.Sprintf("{playerId: %v, ackingFrameId: %v, ackingInputFrameId: %v, lastSentInputFrameId: %v}", playerId, player.AckingFrameId, player.AckingInputFrameId, player.LastSentInputFrameId)) - } + s = append(s, fmt.Sprintf("{playerId: %v, ackingFrameId: %v, ackingInputFrameId: %v, lastSentInputFrameId: %v}", playerId, player.AckingFrameId, player.AckingInputFrameId, player.LastSentInputFrameId)) + } for i := pR.AllPlayerInputsBuffer.StFrameId; i < pR.AllPlayerInputsBuffer.EdFrameId; i++ { tmp := pR.AllPlayerInputsBuffer.GetByFrameId(i) if nil == tmp { @@ -736,27 +736,23 @@ func (pR *Room) StartBattle() { */ battleMainLoop := func() { defer func() { - if r := recover(); r != nil { - Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r)) - } + if r := recover(); r != nil { + Logger.Error("battleMainLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("panic", r)) + } Logger.Info("The `battleMainLoop` is stopped for:", zap.Any("roomId", pR.Id)) pR.onBattleStoppedForSettlement() }() battleMainLoopStartedNanos := utils.UnixtimeNano() - var totalElapsedNanos int64 - totalElapsedNanos = 0 - - // inputFrameIdDownsyncToleranceFrameCnt := int32(1) + totalElapsedNanos := int64(0) Logger.Info("The `battleMainLoop` is started for:", zap.Any("roomId", pR.Id)) for { - pR.Tick++ // It's important to increment "pR.Tick" here such that the "InputFrameDownsync.InputFrameId" is most advanced - if 1 == pR.Tick { + if 0 == pR.Tick { // The legacy frontend code needs this "kickoffFrame" to remove the "ready to start 3-2-1" panel kickoffFrame := pb.RoomDownsyncFrame{ Id: pR.Tick, - RefFrameId: 0, // Hardcoded for now. + RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START, Players: toPbPlayers(pR.Players), Treasures: toPbTreasures(pR.Treasures), Traps: toPbTraps(pR.Traps), @@ -822,7 +818,6 @@ func (pR *Room) StartBattle() { // [WARNING] EDGE CASE HERE: Upon initialization, all of "lastAllConfirmedInputFrameId", "lastAllConfirmedInputFrameIdWithChange" and "anchorInputFrameId" are "-1", thus "candidateToSendInputFrameId" starts with "0", however "inputFrameId: 0" might not have been all confirmed! debugSendingInputFrameId := int32(-1) - // TODO: If a buffered "inputFrame" is inserted but has been non-all-confirmed for a long time, e.g. inserted at "inputFrameId=42" but still non-all-confirmed at "inputFrameId=980", the server should mark that of "inputFrameId=42" as well as send it to the unconfirmed players with an extra "RoomDownsyncFrame" for their FORCE RESET OF REFERENCE STATE for candidateToSendInputFrameId <= lastAllConfirmedInputFrameIdWithChange { tmp := pR.AllPlayerInputsBuffer.GetByFrameId(candidateToSendInputFrameId) if nil == tmp { @@ -848,12 +843,7 @@ func (pR *Room) StartBattle() { } } - /* - if swapped := atomic.CompareAndSwapInt32(&(pR.LastAllConfirmedInputFrameIdWithChange), lastAllConfirmedInputFrameIdWithChange, -1); !swapped { - // "OnBattleCmdReceived" might be updating "pR.LastAllConfirmedInputFrameIdWithChange" simultaneously, don't update here if the old value is no longer valid - Logger.Warn("pR.LastAllConfirmedInputFrameIdWithChange NOT UPDATED:", zap.Any("roomId", pR.Id), zap.Any("refInputFrameId", refInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId)) - } - */ + pR.Tick++ now := utils.UnixtimeNano() elapsedInCalculation := now - stCalculation totalElapsedNanos = (now - battleMainLoopStartedNanos) @@ -953,10 +943,10 @@ func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) { Logger.Info("Key inputFrame change", zap.Any("roomId", pR.Id), zap.Any("inputFrameId", clientInputFrameId), zap.Any("lastInputFrameId", pR.LastAllConfirmedInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId), zap.Any("newInputList", inputFrameDownsync.InputList), zap.Any("lastInputList", pR.LastAllConfirmedInputList)) } atomic.StoreInt32(&(pR.LastAllConfirmedInputFrameId), clientInputFrameId) // [WARNING] It's IMPORTANT that "pR.LastAllConfirmedInputFrameId" is NOT NECESSARILY CONSECUTIVE, i.e. if one of the players disconnects and reconnects within a considerable amount of frame delays! - for i,v := range inputFrameDownsync.InputList { - // To avoid potential misuse of pointers - pR.LastAllConfirmedInputList[i] = v - } + for i, v := range inputFrameDownsync.InputList { + // To avoid potential misuse of pointers + pR.LastAllConfirmedInputList[i] = v + } if pR.inputFrameIdDebuggable(clientInputFrameId) { Logger.Info("inputFrame lifecycle#2[allconfirmed]", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("inputFrameId", clientInputFrameId), zap.Any("StFrameId", pR.AllPlayerInputsBuffer.StFrameId), zap.Any("EdFrameId", pR.AllPlayerInputsBuffer.EdFrameId)) } @@ -1027,12 +1017,12 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) { } battleReadyToStartFrame := pb.RoomDownsyncFrame{ - Id: pR.Tick, - Players: toPbPlayers(pR.Players), - SentAt: utils.UnixtimeMilli(), - RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START, - PlayerMetas: playerMetas, - CountdownNanos: pR.BattleDurationNanos, + Id: pR.Tick, + Players: toPbPlayers(pR.Players), + SentAt: utils.UnixtimeMilli(), + RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_READY_TO_START, + PlayerMetas: playerMetas, + CountdownNanos: pR.BattleDurationNanos, } Logger.Info("Sending out frame for RoomBattleState.PREPARE ", zap.Any("battleReadyToStartFrame", battleReadyToStartFrame)) diff --git a/battle_srv/models/room_heap_manager.go b/battle_srv/models/room_heap_manager.go index e9f4a12..70b58b7 100644 --- a/battle_srv/models/room_heap_manager.go +++ b/battle_srv/models/room_heap_manager.go @@ -109,23 +109,23 @@ func InitRoomHeapManager() { Tick: 0, EffectivePlayerCount: 0, //BattleDurationNanos: int64(5 * 1000 * 1000 * 1000), - BattleDurationNanos: int64(30 * 1000 * 1000 * 1000), - ServerFPS: 60, - Treasures: make(map[int32]*Treasure), - Traps: make(map[int32]*Trap), - GuardTowers: make(map[int32]*GuardTower), - Bullets: make(map[int32]*Bullet), - SpeedShoes: make(map[int32]*SpeedShoe), - Barriers: make(map[int32]*Barrier), - Pumpkins: make(map[int32]*Pumpkin), - AccumulatedLocalIdForBullets: 0, - AllPlayerInputsBuffer: NewRingBuffer(1024), - LastAllConfirmedInputFrameId: -1, - LastAllConfirmedInputFrameIdWithChange: -1, - LastAllConfirmedInputList: make([]uint64, roomCapacity), - InputDelayFrames: 4, - InputScaleFrames: 2, - JoinIndexBooleanArr: joinIndexBooleanArr, + BattleDurationNanos: int64(30 * 1000 * 1000 * 1000), + ServerFPS: 60, + Treasures: make(map[int32]*Treasure), + Traps: make(map[int32]*Trap), + GuardTowers: make(map[int32]*GuardTower), + Bullets: make(map[int32]*Bullet), + SpeedShoes: make(map[int32]*SpeedShoe), + Barriers: make(map[int32]*Barrier), + Pumpkins: make(map[int32]*Pumpkin), + AccumulatedLocalIdForBullets: 0, + AllPlayerInputsBuffer: NewRingBuffer(1024), + LastAllConfirmedInputFrameId: -1, + LastAllConfirmedInputFrameIdWithChange: -1, + LastAllConfirmedInputList: make([]uint64, roomCapacity), + InputDelayFrames: 4, + InputScaleFrames: 2, + JoinIndexBooleanArr: joinIndexBooleanArr, } roomMap[pq[i].Id] = pq[i] pq[i].ChooseStage() diff --git a/battle_srv/pb_output/room_downsync_frame.pb.go b/battle_srv/pb_output/room_downsync_frame.pb.go index 86432fc..7f19732 100644 --- a/battle_srv/pb_output/room_downsync_frame.pb.go +++ b/battle_srv/pb_output/room_downsync_frame.pb.go @@ -1140,9 +1140,8 @@ type RoomDownsyncFrame struct { Traps map[int32]*Trap `protobuf:"bytes,7,rep,name=traps,proto3" json:"traps,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Bullets map[int32]*Bullet `protobuf:"bytes,8,rep,name=bullets,proto3" json:"bullets,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` SpeedShoes map[int32]*SpeedShoe `protobuf:"bytes,9,rep,name=speedShoes,proto3" json:"speedShoes,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Pumpkin map[int32]*Pumpkin `protobuf:"bytes,10,rep,name=pumpkin,proto3" json:"pumpkin,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - GuardTowers map[int32]*GuardTower `protobuf:"bytes,11,rep,name=guardTowers,proto3" json:"guardTowers,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - PlayerMetas map[int32]*PlayerMeta `protobuf:"bytes,12,rep,name=playerMetas,proto3" json:"playerMetas,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + GuardTowers map[int32]*GuardTower `protobuf:"bytes,10,rep,name=guardTowers,proto3" json:"guardTowers,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + PlayerMetas map[int32]*PlayerMeta `protobuf:"bytes,11,rep,name=playerMetas,proto3" json:"playerMetas,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *RoomDownsyncFrame) Reset() { @@ -1240,13 +1239,6 @@ func (x *RoomDownsyncFrame) GetSpeedShoes() map[int32]*SpeedShoe { return nil } -func (x *RoomDownsyncFrame) GetPumpkin() map[int32]*Pumpkin { - if x != nil { - return x.Pumpkin - } - return nil -} - func (x *RoomDownsyncFrame) GetGuardTowers() map[int32]*GuardTower { if x != nil { return x.GuardTowers @@ -1778,8 +1770,8 @@ var file_room_downsync_frame_proto_rawDesc = []byte{ 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0xbb, - 0x0b, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0x9a, + 0x0a, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x46, 0x72, 0x61, @@ -1811,123 +1803,113 @@ var file_room_downsync_frame_proto_rawDesc = []byte{ 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x53, 0x68, 0x6f, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x65, 0x64, 0x53, - 0x68, 0x6f, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x07, 0x70, 0x75, 0x6d, 0x70, 0x6b, 0x69, 0x6e, 0x18, - 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, - 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x75, 0x6d, 0x70, 0x6b, 0x69, - 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, 0x75, 0x6d, 0x70, 0x6b, 0x69, 0x6e, 0x12, - 0x55, 0x0a, 0x0b, 0x67, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x18, 0x0b, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, - 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x47, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, - 0x77, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x67, 0x75, 0x61, 0x72, 0x64, - 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x4d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x72, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, - 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, - 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x1a, 0x53, 0x0a, - 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, - 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x57, 0x0a, 0x0e, 0x54, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x54, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4f, 0x0a, 0x0a, 0x54, - 0x72, 0x61, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x54, 0x72, 0x61, - 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x53, 0x0a, 0x0c, - 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, - 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x1a, 0x59, 0x0a, 0x0f, 0x53, 0x70, 0x65, 0x65, 0x64, 0x53, 0x68, 0x6f, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x53, 0x68, 0x6f, - 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0c, - 0x50, 0x75, 0x6d, 0x70, 0x6b, 0x69, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2e, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, - 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, - 0x50, 0x75, 0x6d, 0x70, 0x6b, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x5b, 0x0a, 0x10, 0x47, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x47, 0x75, 0x61, 0x72, 0x64, 0x54, - 0x6f, 0x77, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, - 0x5b, 0x0a, 0x10, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x56, 0x0a, 0x10, - 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, - 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, - 0x69, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, - 0x64, 0x44, 0x69, 0x72, 0x22, 0x7c, 0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x04, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, - 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, - 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, - 0xca, 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x73, 0x67, - 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, - 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x0a, - 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x24, 0x0a, 0x0d, 0x61, - 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0d, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, - 0x64, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, - 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x61, - 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, - 0x64, 0x12, 0x57, 0x0a, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, - 0x70, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, - 0x72, 0x78, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, - 0x79, 0x6e, 0x63, 0x52, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, - 0x70, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x30, 0x0a, 0x02, 0x68, 0x62, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, - 0x61, 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x02, 0x68, 0x62, 0x22, 0xa4, 0x02, 0x0a, - 0x06, 0x57, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x63, 0x68, - 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, - 0x65, 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, - 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x63, 0x74, 0x12, 0x34, 0x0a, - 0x03, 0x72, 0x64, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x03, - 0x72, 0x64, 0x66, 0x12, 0x5d, 0x0a, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, - 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, - 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x3f, 0x0a, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, - 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, - 0x61, 0x6d, 0x65, 0x42, 0x03, 0x5a, 0x01, 0x2e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x6f, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x0b, 0x67, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, + 0x65, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x72, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, + 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x47, 0x75, + 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, + 0x67, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, 0x55, 0x0a, 0x0b, 0x70, + 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x33, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, + 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, + 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, + 0x61, 0x73, 0x1a, 0x53, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, + 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x57, 0x0a, 0x0e, 0x54, 0x72, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x72, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x54, 0x72, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x4f, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, + 0x78, 0x2e, 0x54, 0x72, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x1a, 0x53, 0x0a, 0x0c, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, + 0x74, 0x65, 0x72, 0x78, 0x2e, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x0f, 0x53, 0x70, 0x65, 0x65, 0x64, 0x53, + 0x68, 0x6f, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x53, 0x70, 0x65, + 0x65, 0x64, 0x53, 0x68, 0x6f, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x1a, 0x5b, 0x0a, 0x10, 0x47, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, + 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x47, 0x75, 0x61, 0x72, 0x64, 0x54, 0x6f, + 0x77, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5b, + 0x0a, 0x10, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, + 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x56, 0x0a, 0x10, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x12, + 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x44, 0x69, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, + 0x44, 0x69, 0x72, 0x22, 0x7c, 0x0a, 0x12, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, + 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, + 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4c, 0x69, 0x73, + 0x74, 0x22, 0x3b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x55, 0x70, + 0x73, 0x79, 0x6e, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xca, + 0x02, 0x0a, 0x05, 0x57, 0x73, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x73, 0x67, 0x49, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, + 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x63, + 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0d, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, + 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, + 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x61, 0x63, + 0x6b, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, + 0x12, 0x57, 0x0a, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, + 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, + 0x78, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, + 0x6e, 0x63, 0x52, 0x15, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, + 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x30, 0x0a, 0x02, 0x68, 0x62, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, + 0x74, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x02, 0x68, 0x62, 0x22, 0xa4, 0x02, 0x0a, 0x06, + 0x57, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x63, 0x68, 0x6f, + 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x65, + 0x63, 0x68, 0x6f, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x63, 0x74, 0x12, 0x34, 0x0a, 0x03, + 0x72, 0x64, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, + 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x03, 0x72, + 0x64, 0x66, 0x12, 0x5d, 0x0a, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, + 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x17, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, + 0x72, 0x61, 0x6d, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x12, 0x3f, 0x0a, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x68, 0x75, + 0x6e, 0x74, 0x65, 0x72, 0x78, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, + 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x62, 0x63, 0x69, 0x46, 0x72, 0x61, + 0x6d, 0x65, 0x42, 0x03, 0x5a, 0x01, 0x2e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1942,7 +1924,7 @@ func file_room_downsync_frame_proto_rawDescGZIP() []byte { return file_room_downsync_frame_proto_rawDescData } -var file_room_downsync_frame_proto_msgTypes = make([]protoimpl.MessageInfo, 30) +var file_room_downsync_frame_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_room_downsync_frame_proto_goTypes = []interface{}{ (*Direction)(nil), // 0: treasurehunterx.Direction (*Vec2D)(nil), // 1: treasurehunterx.Vec2D @@ -1971,9 +1953,8 @@ var file_room_downsync_frame_proto_goTypes = []interface{}{ nil, // 24: treasurehunterx.RoomDownsyncFrame.TrapsEntry nil, // 25: treasurehunterx.RoomDownsyncFrame.BulletsEntry nil, // 26: treasurehunterx.RoomDownsyncFrame.SpeedShoesEntry - nil, // 27: treasurehunterx.RoomDownsyncFrame.PumpkinEntry - nil, // 28: treasurehunterx.RoomDownsyncFrame.GuardTowersEntry - nil, // 29: treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry + nil, // 27: treasurehunterx.RoomDownsyncFrame.GuardTowersEntry + nil, // 28: treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry } var file_room_downsync_frame_proto_depIdxs = []int32{ 1, // 0: treasurehunterx.Polygon2D.Anchor:type_name -> treasurehunterx.Vec2D @@ -1990,29 +1971,27 @@ var file_room_downsync_frame_proto_depIdxs = []int32{ 24, // 11: treasurehunterx.RoomDownsyncFrame.traps:type_name -> treasurehunterx.RoomDownsyncFrame.TrapsEntry 25, // 12: treasurehunterx.RoomDownsyncFrame.bullets:type_name -> treasurehunterx.RoomDownsyncFrame.BulletsEntry 26, // 13: treasurehunterx.RoomDownsyncFrame.speedShoes:type_name -> treasurehunterx.RoomDownsyncFrame.SpeedShoesEntry - 27, // 14: treasurehunterx.RoomDownsyncFrame.pumpkin:type_name -> treasurehunterx.RoomDownsyncFrame.PumpkinEntry - 28, // 15: treasurehunterx.RoomDownsyncFrame.guardTowers:type_name -> treasurehunterx.RoomDownsyncFrame.GuardTowersEntry - 29, // 16: treasurehunterx.RoomDownsyncFrame.playerMetas:type_name -> treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry - 15, // 17: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync - 17, // 18: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync - 14, // 19: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame - 16, // 20: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync - 5, // 21: treasurehunterx.WsResp.bciFrame:type_name -> treasurehunterx.BattleColliderInfo - 3, // 22: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> treasurehunterx.Vec2DList - 4, // 23: treasurehunterx.BattleColliderInfo.StrToPolygon2DListMapEntry.value:type_name -> treasurehunterx.Polygon2DList - 6, // 24: treasurehunterx.RoomDownsyncFrame.PlayersEntry.value:type_name -> treasurehunterx.Player - 8, // 25: treasurehunterx.RoomDownsyncFrame.TreasuresEntry.value:type_name -> treasurehunterx.Treasure - 10, // 26: treasurehunterx.RoomDownsyncFrame.TrapsEntry.value:type_name -> treasurehunterx.Trap - 9, // 27: treasurehunterx.RoomDownsyncFrame.BulletsEntry.value:type_name -> treasurehunterx.Bullet - 11, // 28: treasurehunterx.RoomDownsyncFrame.SpeedShoesEntry.value:type_name -> treasurehunterx.SpeedShoe - 12, // 29: treasurehunterx.RoomDownsyncFrame.PumpkinEntry.value:type_name -> treasurehunterx.Pumpkin - 13, // 30: treasurehunterx.RoomDownsyncFrame.GuardTowersEntry.value:type_name -> treasurehunterx.GuardTower - 7, // 31: treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry.value:type_name -> treasurehunterx.PlayerMeta - 32, // [32:32] is the sub-list for method output_type - 32, // [32:32] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 27, // 14: treasurehunterx.RoomDownsyncFrame.guardTowers:type_name -> treasurehunterx.RoomDownsyncFrame.GuardTowersEntry + 28, // 15: treasurehunterx.RoomDownsyncFrame.playerMetas:type_name -> treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry + 15, // 16: treasurehunterx.WsReq.inputFrameUpsyncBatch:type_name -> treasurehunterx.InputFrameUpsync + 17, // 17: treasurehunterx.WsReq.hb:type_name -> treasurehunterx.HeartbeatUpsync + 14, // 18: treasurehunterx.WsResp.rdf:type_name -> treasurehunterx.RoomDownsyncFrame + 16, // 19: treasurehunterx.WsResp.inputFrameDownsyncBatch:type_name -> treasurehunterx.InputFrameDownsync + 5, // 20: treasurehunterx.WsResp.bciFrame:type_name -> treasurehunterx.BattleColliderInfo + 3, // 21: treasurehunterx.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> treasurehunterx.Vec2DList + 4, // 22: treasurehunterx.BattleColliderInfo.StrToPolygon2DListMapEntry.value:type_name -> treasurehunterx.Polygon2DList + 6, // 23: treasurehunterx.RoomDownsyncFrame.PlayersEntry.value:type_name -> treasurehunterx.Player + 8, // 24: treasurehunterx.RoomDownsyncFrame.TreasuresEntry.value:type_name -> treasurehunterx.Treasure + 10, // 25: treasurehunterx.RoomDownsyncFrame.TrapsEntry.value:type_name -> treasurehunterx.Trap + 9, // 26: treasurehunterx.RoomDownsyncFrame.BulletsEntry.value:type_name -> treasurehunterx.Bullet + 11, // 27: treasurehunterx.RoomDownsyncFrame.SpeedShoesEntry.value:type_name -> treasurehunterx.SpeedShoe + 13, // 28: treasurehunterx.RoomDownsyncFrame.GuardTowersEntry.value:type_name -> treasurehunterx.GuardTower + 7, // 29: treasurehunterx.RoomDownsyncFrame.PlayerMetasEntry.value:type_name -> treasurehunterx.PlayerMeta + 30, // [30:30] is the sub-list for method output_type + 30, // [30:30] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_room_downsync_frame_proto_init() } @@ -2268,7 +2247,7 @@ func file_room_downsync_frame_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_room_downsync_frame_proto_rawDesc, NumEnums: 0, - NumMessages: 30, + NumMessages: 29, NumExtensions: 0, NumServices: 0, }, diff --git a/frontend/assets/resources/pbfiles/room_downsync_frame.proto b/frontend/assets/resources/pbfiles/room_downsync_frame.proto index ed1e4b4..9f414cd 100644 --- a/frontend/assets/resources/pbfiles/room_downsync_frame.proto +++ b/frontend/assets/resources/pbfiles/room_downsync_frame.proto @@ -126,9 +126,8 @@ message RoomDownsyncFrame { map traps = 7; map bullets = 8; map speedShoes = 9; - map pumpkin = 10; - map guardTowers = 11; - map playerMetas = 12; + map guardTowers = 10; + map playerMetas = 11; } message InputFrameUpsync { diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index c6d4630..020e9a8 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -2991,9 +2991,6 @@ "loadingPrefab": { "__uuid__": "f2a3cece-30bf-4f62-bc20-34d44a9ddf98" }, - "wechatLoginTips": { - "__id__": 68 - }, "_id": "51YNpecnJBea8vzAxGdkv2" }, { diff --git a/frontend/assets/scenes/wechatGameLogin.fire b/frontend/assets/scenes/wechatGameLogin.fire deleted file mode 100644 index 141f368..0000000 --- a/frontend/assets/scenes/wechatGameLogin.fire +++ /dev/null @@ -1,1706 +0,0 @@ -[ - { - "__type__": "cc.SceneAsset", - "_name": "", - "_objFlags": 0, - "_native": "", - "scene": { - "__id__": 1 - } - }, - { - "__type__": "cc.Scene", - "_objFlags": 0, - "_parent": null, - "_children": [ - { - "__id__": 2 - } - ], - "_active": false, - "_level": 0, - "_components": [], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 0, - "height": 0 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_is3DNode": true, - "groupIndex": 0, - "autoReleaseAssets": false, - "_id": "475b849b-44b3-4390-982d-bd0d9e695093", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Node", - "_name": "Canvas", - "_objFlags": 0, - "_parent": { - "__id__": 1 - }, - "_children": [ - { - "__id__": 3 - }, - { - "__id__": 5 - }, - { - "__id__": 7 - }, - { - "__id__": 12 - }, - { - "__id__": 23 - } - ], - "_active": true, - "_level": 1, - "_components": [ - { - "__id__": 34 - }, - { - "__id__": 35 - }, - { - "__id__": 36 - }, - { - "__id__": 37 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 640, - "height": 960 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "88kscZWXFCIZtNFekdSP/o", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 320, - 480, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Node", - "_name": "Main Camera", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_children": [], - "_active": true, - "_level": 0, - "_components": [ - { - "__id__": 4 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 0, - "height": 0 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "f1+7GE/nBJIrtiDgZie30q", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 0, - 202.36995509211857, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Camera", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 3 - }, - "_enabled": true, - "_cullingMask": 4294967295, - "_clearFlags": 0, - "_backgroundColor": { - "__type__": "cc.Color", - "r": 0, - "g": 0, - "b": 0, - "a": 255 - }, - "_depth": 0, - "_zoomRatio": 1, - "_targetTexture": null, - "_fov": 60, - "_orthoSize": 10, - "_nearClip": 1, - "_farClip": 4096, - "_ortho": true, - "_rect": { - "__type__": "cc.Rect", - "x": 0, - "y": 0, - "width": 1, - "height": 1 - }, - "_renderStages": 1, - "_id": "7eS1XfnvVCVJh7J5AVbnd8" - }, - { - "__type__": "cc.Node", - "_name": "Tips", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_children": [], - "_active": true, - "_level": 2, - "_components": [ - { - "__id__": 6 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 0, - "g": 0, - "b": 0, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 600, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "a282mCIUBOmqmf3o22ZSKE", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 125, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 5 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "", - "_N$string": "", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 3, - "_N$cacheMode": 0, - "_id": "3ejZjl6HVA1rSY1hSxd8H8" - }, - { - "__type__": "cc.Node", - "_name": "Decorations", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_children": [ - { - "__id__": 8 - }, - { - "__id__": 10 - } - ], - "_active": true, - "_level": 2, - "_components": [], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 0, - "height": 0 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "c3AZmU3YpOTYhMw/8haSH8", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Node", - "_name": "Logo", - "_objFlags": 0, - "_parent": { - "__id__": 7 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 9 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 424, - "height": 168 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "9bq54961lFtqOPk3DEyZTr", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 260, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 8 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "9e564509-0a36-4f01-a833-79ff8f2e8328" - }, - "_type": 0, - "_sizeMode": 1, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": { - "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" - }, - "_id": "25w02YAVpPTKm8JUfP1Iyn" - }, - { - "__type__": "cc.Node", - "_name": "Bolldozer", - "_objFlags": 0, - "_parent": { - "__id__": 7 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 11 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 248, - "height": 513 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "94YmH2GARPAoCvyPWeCbBo", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - -180, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 10 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "2158ddd8-02de-47c2-ade8-f701288c22e5" - }, - "_type": 0, - "_sizeMode": 1, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": { - "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" - }, - "_id": "b4I8o4N61CJYbad3GUjAvQ" - }, - { - "__type__": "cc.Node", - "_name": "DownloadProgress", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_children": [ - { - "__id__": 13 - }, - { - "__id__": 15 - }, - { - "__id__": 17 - }, - { - "__id__": 19 - } - ], - "_active": true, - "_level": 2, - "_components": [ - { - "__id__": 21 - }, - { - "__id__": 22 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 300, - "height": 32 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "abQHOjM2dO5rBkz6fGiJKP", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - -435, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Node", - "_name": "bar", - "_objFlags": 0, - "_parent": { - "__id__": 12 - }, - "_children": [], - "_active": true, - "_level": 0, - "_components": [ - { - "__id__": 14 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 19, - "g": 148, - "b": 35, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 0, - "height": 32 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "35HX0AAMpMCYACcb2fdXjq", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - -150, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 13 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "67e68bc9-dad5-4ad9-a2d8-7e03d458e32f" - }, - "_type": 1, - "_sizeMode": 0, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": null, - "_id": "65DRQh2TdB24L0axkKhYyy" - }, - { - "__type__": "cc.Node", - "_name": "Written", - "_objFlags": 0, - "_parent": { - "__id__": 12 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 16 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 10.66, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 1, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "38DI8Gj5lAEapdfbgcQHRE", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - -15, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 15 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "-", - "_N$string": "-", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "95L3mPi11Ovojqz7HD7nmk" - }, - { - "__type__": "cc.Node", - "_name": "Separater", - "_objFlags": 0, - "_parent": { - "__id__": 12 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 18 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 8.89, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "31gp1c7+hPTacRYMQzCtoI", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 17 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "/", - "_N$string": "/", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "759pNtiHdIB5widXzhhIdV" - }, - { - "__type__": "cc.Node", - "_name": "ExpectedToWrite", - "_objFlags": 0, - "_parent": { - "__id__": 12 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 20 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 10.66, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "20R1sB1uNPf5gsMbmFaHxZ", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 10, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 19 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "-", - "_N$string": "-", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "dbFyA15cdL+btd6rNGJcWg" - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 12 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "88e79fd5-96b4-4a77-a1f4-312467171014" - }, - "_type": 1, - "_sizeMode": 0, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": null, - "_id": "593TaHj0FJc7dURtmLMIk/" - }, - { - "__type__": "cc.ProgressBar", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 12 - }, - "_enabled": true, - "_N$totalLength": 300, - "_N$barSprite": { - "__id__": 14 - }, - "_N$mode": 0, - "_N$progress": 0, - "_N$reverse": false, - "_id": "09vvE4cMRJZ7N0o2D6SeTZ" - }, - { - "__type__": "cc.Node", - "_name": "HandlerProgress", - "_objFlags": 0, - "_parent": { - "__id__": 2 - }, - "_children": [ - { - "__id__": 24 - }, - { - "__id__": 26 - }, - { - "__id__": 28 - }, - { - "__id__": 30 - } - ], - "_active": true, - "_level": 2, - "_components": [ - { - "__id__": 32 - }, - { - "__id__": 33 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 300, - "height": 30 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "c0nxsTzCtKa4OX78IBsR0Q", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - -367, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Node", - "_name": "bar", - "_objFlags": 0, - "_parent": { - "__id__": 23 - }, - "_children": [], - "_active": true, - "_level": 0, - "_components": [ - { - "__id__": 25 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 202, - "g": 115, - "b": 21, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 0, - "height": 32 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "24ZKpM0FdNQZYiIRF0KyjW", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - -150, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 24 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "67e68bc9-dad5-4ad9-a2d8-7e03d458e32f" - }, - "_type": 1, - "_sizeMode": 0, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": null, - "_id": "15MJsSQ2RBcYNOlHOGBVtf" - }, - { - "__type__": "cc.Node", - "_name": "Handled", - "_objFlags": 0, - "_parent": { - "__id__": 23 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 27 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 10.66, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 1, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "8fupG1MHNARIAlsizaxHZ4", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - -15, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 26 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "-", - "_N$string": "-", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "ddll07J9hAoZQDGbNbZK/r" - }, - { - "__type__": "cc.Node", - "_name": "Separater", - "_objFlags": 0, - "_parent": { - "__id__": 23 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 29 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 8.89, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "3c/av+sOZLMbxWRhODcKmG", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 28 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "/", - "_N$string": "/", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "c8z9PIEA1ObK11KdTNPVIP" - }, - { - "__type__": "cc.Node", - "_name": "ToHandle", - "_objFlags": 0, - "_parent": { - "__id__": 23 - }, - "_children": [], - "_active": true, - "_level": 3, - "_components": [ - { - "__id__": 31 - } - ], - "_prefab": null, - "_opacity": 255, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 10.66, - "height": 50.4 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0.5 - }, - "_eulerAngles": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_skewX": 0, - "_skewY": 0, - "_is3DNode": false, - "groupIndex": 0, - "_id": "18hLtXEjBMMbL7WX1voVDu", - "_trs": { - "__type__": "TypedArray", - "ctor": "Float64Array", - "array": [ - 10, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1 - ] - } - }, - { - "__type__": "cc.Label", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 30 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_useOriginalSize": false, - "_string": "-", - "_N$string": "-", - "_fontSize": 32, - "_lineHeight": 40, - "_enableWrapText": true, - "_N$file": null, - "_isSystemFontUsed": true, - "_spacingX": 0, - "_batchAsBitmap": false, - "_N$horizontalAlign": 1, - "_N$verticalAlign": 1, - "_N$fontFamily": "Arial", - "_N$overflow": 0, - "_N$cacheMode": 0, - "_id": "5b+RrNshNILbBAjAAyqvfN" - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 23 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "88e79fd5-96b4-4a77-a1f4-312467171014" - }, - "_type": 1, - "_sizeMode": 0, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_atlas": null, - "_id": "b1H93UoY9CpalSJVXUvObw" - }, - { - "__type__": "cc.ProgressBar", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 23 - }, - "_enabled": true, - "_N$totalLength": 300, - "_N$barSprite": { - "__id__": 25 - }, - "_N$mode": 0, - "_N$progress": 0, - "_N$reverse": false, - "_id": "d44CusjQ1NeL3CU/6/pYju" - }, - { - "__type__": "cc.Canvas", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 2 - }, - "_enabled": true, - "_designResolution": { - "__type__": "cc.Size", - "width": 640, - "height": 960 - }, - "_fitWidth": true, - "_fitHeight": false, - "_id": "45irrwnU1MAaF3kwQFnJ++" - }, - { - "__type__": "cc.Widget", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 2 - }, - "_enabled": false, - "alignMode": 0, - "_target": null, - "_alignFlags": 45, - "_left": 0, - "_right": 0, - "_top": 0, - "_bottom": 0, - "_verticalCenter": 0, - "_horizontalCenter": 0, - "_isAbsLeft": true, - "_isAbsRight": true, - "_isAbsTop": true, - "_isAbsBottom": true, - "_isAbsHorizontalCenter": true, - "_isAbsVerticalCenter": true, - "_originalWidth": 640, - "_originalHeight": 960, - "_id": "femrjqTNREkZw7OPQcF2dD" - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 2 - }, - "_enabled": true, - "_materials": [ - { - "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" - } - ], - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, - "_spriteFrame": { - "__uuid__": "7838f276-ab48-445a-b858-937dd27d9520" - }, - "_type": 0, - "_sizeMode": 0, - "_fillType": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": false, - "_atlas": null, - "_id": "e2yoOcT3ND2YHlCSRJKZ5q" - }, - { - "__type__": "8264fty40hF5JqzW/+5pWHu", - "_name": "", - "_objFlags": 0, - "node": { - "__id__": 2 - }, - "_enabled": true, - "cavasNode": { - "__id__": 2 - }, - "backgroundNode": { - "__id__": 2 - }, - "loadingPrefab": { - "__uuid__": "f2a3cece-30bf-4f62-bc20-34d44a9ddf98" - }, - "tipsLabel": { - "__id__": 6 - }, - "downloadProgress": { - "__id__": 22 - }, - "writtenBytes": { - "__id__": 16 - }, - "expectedToWriteBytes": { - "__id__": 20 - }, - "handlerProgress": { - "__id__": 33 - }, - "handledUrlsCount": { - "__id__": 27 - }, - "toHandledUrlsCount": { - "__id__": 31 - }, - "_id": "c0EVg/B8RMWLA48jmAqxQr" - } -] \ No newline at end of file diff --git a/frontend/assets/scenes/wechatGameLogin.fire.meta b/frontend/assets/scenes/wechatGameLogin.fire.meta deleted file mode 100644 index 2f2638d..0000000 --- a/frontend/assets/scenes/wechatGameLogin.fire.meta +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ver": "1.2.5", - "uuid": "475b849b-44b3-4390-982d-bd0d9e695093", - "asyncLoadAssets": false, - "autoReleaseAssets": false, - "subMetas": {} -} \ No newline at end of file diff --git a/frontend/assets/scripts/BasePlayer.js b/frontend/assets/scripts/BasePlayer.js index 6aa7c3d..2d1840b 100644 --- a/frontend/assets/scripts/BasePlayer.js +++ b/frontend/assets/scripts/BasePlayer.js @@ -35,11 +35,6 @@ module.export = cc.Class({ // LIFE-CYCLE CALLBACKS: start() { const self = this; - self.contactedControlledPlayers = []; - self.contactedNPCPlayers = []; - - self.computedNewDifferentPosLocalToParentWithinCurrentFrame = null; - self.actionMangerSingleton = new cc.ActionManager(); self.activeDirection = { dx: 0, dy: 0 @@ -60,7 +55,6 @@ module.export = cc.Class({ }; const canvasNode = self.mapNode.parent; self.mapIns = self.mapNode.getComponent("Map"); - self.contactedBarriers = []; const joystickInputControllerScriptIns = canvasNode.getComponent("TouchEventsManager"); self.ctrl = joystickInputControllerScriptIns; self.animComp = self.node.getComponent(cc.Animation); @@ -92,295 +86,10 @@ module.export = cc.Class({ } }, - _addContactedBarrier(collider) { - const self = this; - if (!self.contactedBarriers) { - cc.log("self.contactedBarriers is null or undefined" + self.contactedBarriers) - } - for (let contactedBarrier of self.contactedBarriers) { - if (contactedBarrier._id == collider._id) { - return false; - } - } - self.contactedBarriers.push(collider); - return true; - }, - - _removeContactedBarrier(collider) { - const self = this; - self.contactedBarriers = self.contactedBarriers.filter((contactedBarrier) => { - return contactedBarrier._id != collider._id; - }); - return true; - }, - - _addContactedControlledPlayers(comp) { - const self = this; - for (let aComp of self.contactedControlledPlayers) { - if (aComp.uuid == comp.uuid) { - return false; - } - } - self.contactedControlledPlayers.push(comp); - return true; - }, - - _removeContactedControlledPlayer(comp) { - const self = this; - self.contactedControlledPlayers = self.contactedControlledPlayers.filter((aComp) => { - return aComp.uuid != comp.uuid; - }); - return true; - }, - - _addContactedNPCPlayers(comp) { - const self = this; - for (let aComp of self.contactedNPCPlayers) { - if (aComp.uuid == comp.uuid) { - return false; - } - } - self.contactedNPCPlayers.push(comp); - return true; - }, - - _removeContactedNPCPlayer(comp) { - const self = this; - self.contactedNPCPlayers = self.contactedNPCPlayers.filter((aComp) => { - return aComp.uuid != comp.uuid; - }); - return true; - }, - - _canMoveBy(vecToMoveBy) { - const self = this; - const computedNewDifferentPosLocalToParentWithinCurrentFrame = self.node.position.add(vecToMoveBy); - self.computedNewDifferentPosLocalToParentWithinCurrentFrame = computedNewDifferentPosLocalToParentWithinCurrentFrame; - - if (tileCollisionManager.isOutOfMapNode(self.mapNode, computedNewDifferentPosLocalToParentWithinCurrentFrame)) { - return false; - } - - const currentSelfColliderCircle = self.node.getComponent(cc.CircleCollider); - let nextSelfColliderCircle = null; - if (0 < self.contactedBarriers.length) { - /* To avoid unexpected buckling. */ - const mutatedVecToMoveBy = vecToMoveBy.mul(5); // To help it escape the engaged `contactedBarriers`. - nextSelfColliderCircle = { - position: self.node.position.add(mutatedVecToMoveBy).add(currentSelfColliderCircle.offset), - radius: currentSelfColliderCircle.radius, - }; - } else { - nextSelfColliderCircle = { - position: computedNewDifferentPosLocalToParentWithinCurrentFrame.add(currentSelfColliderCircle.offset), - radius: currentSelfColliderCircle.radius, - }; - } - - for (let contactedBarrier of self.contactedBarriers) { - let contactedBarrierPolygonLocalToParentWithinCurrentFrame = []; - for (let p of contactedBarrier.points) { - contactedBarrierPolygonLocalToParentWithinCurrentFrame.push(contactedBarrier.node.position.add(p)); - } - if (cc.Intersection.pointInPolygon(nextSelfColliderCircle.position, contactedBarrierPolygonLocalToParentWithinCurrentFrame)) { - // Make sure that the player is "leaving" the PolygonCollider. - return false; - } - if (cc.Intersection.polygonCircle(contactedBarrierPolygonLocalToParentWithinCurrentFrame, nextSelfColliderCircle)) { - if (null == self.firstContactedEdge) { - return false; - } - if (null != self.firstContactedEdge && self.firstContactedEdge.associatedBarrier != contactedBarrier) { - const res = self._calculateTangentialMovementAttrs(nextSelfColliderCircle, contactedBarrier); - if (null == res.contactedEdge) { - // Otherwise, the current movement is going to transit smoothly onto the next PolygonCollider. - return false; - } - } - } - } - - return true; - - /* - * In a subclass, use - * - * _canMoveBy(vecToMoveBy) { - * BasePlayer.prototype._canMoveBy.call(this, vecToMoveBy); - * // Customized codes. - * } - * - * Reference http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/class.html#override - */ - }, - - _calculateTangentialMovementAttrs(currentSelfColliderCircle, contactedBarrier) { - /* - * Theoretically when the `contactedBarrier` is a convex polygon and the `PlayerCollider` is a circle, there can be only 1 `contactedEdge` for each `contactedBarrier`. Except only for around the corner. - * - * We should avoid the possibility of players hitting the "corners of convex polygons" by map design wherever & whenever possible. - * - */ - const self = this; - const sDir = self.activeDirection; - const currentSelfColliderCircleCentrePos = (currentSelfColliderCircle.position ? currentSelfColliderCircle.position : self.node.position.add(currentSelfColliderCircle.offset)); - const currentSelfColliderCircleRadius = currentSelfColliderCircle.radius; - let contactedEdgeCandidateList = []; - let skinDepthThreshold = 0.45*currentSelfColliderCircleRadius; - for (let i = 0; i < contactedBarrier.points.length; ++i) { - const stPoint = contactedBarrier.points[i].add(contactedBarrier.offset).add(contactedBarrier.node.position); - const edPoint = (i == contactedBarrier.points.length - 1 ? contactedBarrier.points[0].add(contactedBarrier.offset).add(contactedBarrier.node.position) : contactedBarrier.points[1 + i].add(contactedBarrier.offset).add(contactedBarrier.node.position)); - const tmpVSt = stPoint.sub(currentSelfColliderCircleCentrePos); - const tmpVEd = edPoint.sub(currentSelfColliderCircleCentrePos); - const crossProdScalar = tmpVSt.cross(tmpVEd); - if (0 < crossProdScalar) { - // If moving parallel along `st <-> ed`, the trajectory of `currentSelfColliderCircleCentrePos` will cut inside the polygon. - continue; - } - const dis = cc.Intersection.pointLineDistance(currentSelfColliderCircleCentrePos, stPoint, edPoint, true); - if (dis > currentSelfColliderCircleRadius) continue; - if (dis < skinDepthThreshold) continue; - contactedEdgeCandidateList.push({ - st: stPoint, - ed: edPoint, - associatedBarrier: contactedBarrier, - }); - } - let contactedEdge = null; - let contactedEdgeDir = null; - let largestInnerProdAbs = Number.MIN_VALUE; - - if (0 < contactedEdgeCandidateList.length) { - const sDirMag = Math.sqrt(sDir.dx * sDir.dx + sDir.dy * sDir.dy); - for (let contactedEdgeCandidate of contactedEdgeCandidateList) { - const tmp = contactedEdgeCandidate.ed.sub(contactedEdgeCandidate.st); - const contactedEdgeDirCandidate = { - dx: tmp.x, - dy: tmp.y, - }; - const contactedEdgeDirCandidateMag = Math.sqrt(contactedEdgeDirCandidate.dx * contactedEdgeDirCandidate.dx + contactedEdgeDirCandidate.dy * contactedEdgeDirCandidate.dy); - const innerDotProd = (sDir.dx * contactedEdgeDirCandidate.dx + sDir.dy * contactedEdgeDirCandidate.dy)/(sDirMag * contactedEdgeDirCandidateMag); - const innerDotProdThresholdMag = 0.7; - if ((0 > innerDotProd && innerDotProd > -innerDotProdThresholdMag) || (0 < innerDotProd && innerDotProd < innerDotProdThresholdMag)) { - // Intentionally left blank, in this case the player is trying to escape from the `contactedEdge`. - continue; - } else if (innerDotProd > 0) { - const abs = Math.abs(innerDotProd); - if (abs > largestInnerProdAbs) { - contactedEdgeDir = contactedEdgeDirCandidate; - contactedEdge = contactedEdgeCandidate; - } - } else { - const abs = Math.abs(innerDotProd); - if (abs > largestInnerProdAbs) { - contactedEdgeDir = { - dx: -contactedEdgeDirCandidate.dx, - dy: -contactedEdgeDirCandidate.dy, - }; - contactedEdge = contactedEdgeCandidate; - } - } - } - } - return { - contactedEdgeDir: contactedEdgeDir, - contactedEdge: contactedEdge, - }; - }, - - _calculateVecToMoveByWithChosenDir(elapsedTime, sDir) { - if (0 == sDir.dx && 0 == sDir.dy) { - return cc.v2(); - } - const self = this; - const distanceToMove = (self.speed * elapsedTime); - const denominator = Math.sqrt(sDir.dx * sDir.dx + sDir.dy * sDir.dy); - const unitProjDx = (sDir.dx / denominator); - const unitProjDy = (sDir.dy / denominator); - return cc.v2( - distanceToMove * unitProjDx, - distanceToMove * unitProjDy, - ); - }, - - _calculateVecToMoveBy(elapsedTime) { - const self = this; - // Note that `sDir` used in this method MUST BE a copy in RAM. - let sDir = { - dx: self.activeDirection.dx, - dy: self.activeDirection.dy, - }; - - if (0 == sDir.dx && 0 == sDir.dy) { - return cc.v2(); - } - - self.firstContactedEdge = null; // Reset everytime (temporary algorithm design, might change later). - if (0 < self.contactedBarriers.length) { - /* - * Hardcoded to take care of only the 1st `contactedEdge` of the 1st `contactedBarrier` for now. Each `contactedBarrier` must be "counterclockwisely convex polygonal", otherwise sliding doesn't work! - * - */ - const contactedBarrier = self.contactedBarriers[0]; - const currentSelfColliderCircle = self.node.getComponent(cc.CircleCollider); - const res = self._calculateTangentialMovementAttrs(currentSelfColliderCircle, contactedBarrier); - if (res.contactedEdge) { - self.firstContactedEdge = res.contactedEdge; - sDir = res.contactedEdgeDir; - } - } - return self._calculateVecToMoveByWithChosenDir(elapsedTime, sDir); - }, - update(dt) { - const self = this; - const vecToMoveBy = self._calculateVecToMoveBy(self.mapIns.rollbackEstimatedDt); // To be consistent w.r.t. rollback dynamics - // console.log("activeDirection=", self.activeDirection, "vecToMoveBy=", vecToMoveBy, ", computedNewDifferentPosLocalToParentWithinCurrentFrame=", self.computedNewDifferentPosLocalToParentWithinCurrentFrame); - if (self._canMoveBy(vecToMoveBy)) { - self.node.position = self.computedNewDifferentPosLocalToParentWithinCurrentFrame; - } }, lateUpdate(dt) { - const self = this; - const now = new Date().getTime(); - self.lastMovedAt = now; - }, - - onCollisionEnter(other, self) { - const playerScriptIns = self.node.getComponent("SelfPlayer"); - switch (other.node.name) { - case "NPCPlayer": - if ("NPCPlayer" != self.node.name) { - other.node.getComponent('NPCPlayer').showProfileTrigger(); - } - playerScriptIns._addContactedNPCPlayers(other); - break; - case "PolygonBoundaryBarrier": - playerScriptIns._addContactedBarrier(other); - break; - default: - break; - } - }, - - onCollisionStay(other, self) { - // TBD. - }, - - onCollisionExit(other, self) { - const playerScriptIns = self.getComponent("SelfPlayer"); - switch (other.node.name) { - case "NPCPlayer": - other.node.getComponent('NPCPlayer').hideProfileTrigger(); - playerScriptIns._removeContactedNPCPlayer(other); - break; - case "PolygonBoundaryBarrier": - playerScriptIns._removeContactedBarrier(other); - break; - default: - break; - } }, _generateRandomDirection() { diff --git a/frontend/assets/scripts/FindingPlayer.js b/frontend/assets/scripts/FindingPlayer.js index e6f145b..0262366 100644 --- a/frontend/assets/scripts/FindingPlayer.js +++ b/frontend/assets/scripts/FindingPlayer.js @@ -26,19 +26,6 @@ cc.Class({ // LIFE-CYCLE CALLBACKS: onLoad() { - // WARNING: 不能保证在ws连接成功并且拿到boundRoomId后才运行到此处。 - if (cc.sys.platform == cc.sys.WECHAT_GAME) { - const boundRoomId = window.getBoundRoomIdFromPersistentStorage(); - const wxToShareMessage = { - title: '夺宝大作战', - imageUrl: 'https://mmocgame.qpic.cn/wechatgame/ibxA6JVNslX02zq6aAWCZiaWTXLYGorrVgUszo3WH1oL1CFDcFU7VKPRXPFiadxagMR/0', - imageUrlId: 'FiLZpa5FT5GgEeEagzGBsA', - query: 'expectedRoomId=' + boundRoomId, - }; - console.warn("The boundRoomId for sharing: ", boundRoomId, " wxToShareMessage ", wxToShareMessage); - wx.showShareMenu(); - wx.onShareAppMessage(() => (wxToShareMessage)); - } }, init() { @@ -73,11 +60,7 @@ cc.Class({ exitBtnOnClick(evt) { window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); window.closeWSConnection(); - if (cc.sys.platform == cc.sys.WECHAT_GAME) { - cc.director.loadScene('wechatGameLogin'); - } else { - cc.director.loadScene('login'); - } + cc.director.loadScene('login'); }, updatePlayersInfo(playerMetas) { diff --git a/frontend/assets/scripts/Login.js b/frontend/assets/scripts/Login.js index c8b2b77..a6da459 100644 --- a/frontend/assets/scripts/Login.js +++ b/frontend/assets/scripts/Login.js @@ -60,11 +60,7 @@ cc.Class({ loadingPrefab: { default: null, type: cc.Prefab - }, - wechatLoginTips: { - default: null, - type: cc.Label, - }, + } }, // LIFE-CYCLE CALLBACKS: @@ -89,22 +85,18 @@ cc.Class({ self.getRetCodeList(); self.getRegexList(); - const isUsingX5BlinkKernelOrWebkitWeChatKernel = window.isUsingX5BlinkKernelOrWebkitWeChatKernel(); - //const isUsingX5BlinkKernelOrWebkitWeChatKernel = true; - if (!CC_DEBUG) { - self.phoneNumberTips.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - self.smsLoginCaptchaButton.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; + self.phoneNumberTips.active = true; + self.smsLoginCaptchaButton.active = true; - self.captchaTips.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - self.phoneCountryCodeInput.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - self.phoneNumberInput.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - self.smsLoginCaptchaInput.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; + self.captchaTips.active = true; + self.phoneCountryCodeInput.active = true; + self.phoneNumberInput.active = true; + self.smsLoginCaptchaInput.active = true; - self.phoneLabel.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - self.smsLoginCaptchaLabel.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; + self.phoneLabel.active = true; + self.smsLoginCaptchaLabel.active = true; - self.loginButton.active = !isUsingX5BlinkKernelOrWebkitWeChatKernel; - } + self.loginButton.active = true; self.checkPhoneNumber = self.checkPhoneNumber.bind(self); self.checkIntAuthTokenExpire = self.checkIntAuthTokenExpire.bind(self); self.checkCaptcha = self.checkCaptcha.bind(self); @@ -125,15 +117,13 @@ cc.Class({ cc.error(err.message || err); return; } - if (false == (cc.sys.platform == cc.sys.WECHAT_GAME)) { - // Otherwise, `window.RoomDownsyncFrame` is already assigned. - let protoRoot = new protobuf.Root; - window.protobuf.parse(textAsset.text, protoRoot); - window.RoomDownsyncFrame = protoRoot.lookupType("treasurehunterx.RoomDownsyncFrame"); - window.BattleColliderInfo = protoRoot.lookupType("treasurehunterx.BattleColliderInfo"); - window.WsReq = protoRoot.lookupType("treasurehunterx.WsReq"); - window.WsResp = protoRoot.lookupType("treasurehunterx.WsResp"); - } + // Otherwise, `window.RoomDownsyncFrame` is already assigned. + let protoRoot = new protobuf.Root; + window.protobuf.parse(textAsset.text, protoRoot); + window.RoomDownsyncFrame = protoRoot.lookupType("treasurehunterx.RoomDownsyncFrame"); + window.BattleColliderInfo = protoRoot.lookupType("treasurehunterx.BattleColliderInfo"); + window.WsReq = protoRoot.lookupType("treasurehunterx.WsReq"); + window.WsResp = protoRoot.lookupType("treasurehunterx.WsResp"); self.checkIntAuthTokenExpire().then( () => { const intAuthToken = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')).intAuthToken; @@ -141,19 +131,6 @@ cc.Class({ }, () => { window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - if ( (CC_DEBUG || isUsingX5BlinkKernelOrWebkitWeChatKernel) ) { - if (null != qDict && qDict["code"]) { - const code = qDict["code"]; - console.log("Got the wx authcode: ", code, "while at full url: " + window.location.href); - self.useWXCodeLogin(code); - } else { - if (isUsingX5BlinkKernelOrWebkitWeChatKernel) { - self.getWechatCode(null); - } else { - // Deliberately left blank. - } - } - } } ); }); @@ -356,46 +333,10 @@ cc.Class({ }); }, - onWechatLoggedIn(res) { - const self = this; - if (res.ret === self.retCodeDict.OK) { - self.enableInteractiveControls(false); - const date = Number(res.expiresAt); - const selfPlayer = { - expiresAt: date, - playerId: res.playerId, - intAuthToken: res.intAuthToken, - displayName: res.displayName, - avatar: res.avatar, - } - cc.sys.localStorage.setItem('selfPlayer', JSON.stringify(selfPlayer)); - - const qDict = window.getQueryParamDict(); - const expectedRoomId = qDict["expectedRoomId"]; - if (null != expectedRoomId) { - console.log("onWechatLoggedIn using expectedRoomId == " + expectedRoomId); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - } - // To remove "code=XXX" in "query string". - window.history.replaceState(qDict, null, window.location.pathname); - self.useTokenLogin(res.intAuthToken); - } else { - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.wechatLoginTips.string = constants.ALERT.TIP_LABEL.WECHAT_LOGIN_FAILS + ", errorCode = " + res.ret; - // To remove "code=XXX" in "query string". - window.history.replaceState({}, null, window.location.pathname); - } - }, - onLoggedIn(res) { const self = this; cc.log(`OnLoggedIn ${JSON.stringify(res)}.`) if (res.ret === self.retCodeDict.OK) { - if(window.isUsingX5BlinkKernelOrWebkitWeChatKernel()) { - window.initWxSdk = self.initWxSdk.bind(self); - window.initWxSdk(); - } self.enableInteractiveControls(false); const date = Number(res.expiresAt); const selfPlayer = { @@ -452,105 +393,4 @@ cc.Class({ } } }, - - useWXCodeLogin(_code) { - const self = this; - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.WECHAT + constants.ROUTE_PATH.LOGIN, - type: "POST", - data: { - code: _code - }, - success: function(res) { - self.onWechatLoggedIn(res); - }, - error: function(xhr, status, errMsg) { - console.log("Login attempt `useWXCodeLogin` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.wechatLoginTips.string = constants.ALERT.TIP_LABEL.WECHAT_LOGIN_FAILS + ", errorMsg =" + errMsg; - window.history.replaceState({}, null, window.location.pathname); - }, - }); - }, - - getWechatCode(evt) { - let self = this; - self.wechatLoginTips.string = ""; - const wechatServerEndpoint = wechatAddress.PROTOCOL + "://" + wechatAddress.HOST + ((null != wechatAddress.PORT && "" != wechatAddress.PORT.trim()) ? (":" + wechatAddress.PORT) : ""); - const url = wechatServerEndpoint + constants.WECHAT.AUTHORIZE_PATH + "?" + wechatAddress.APPID_LITERAL + "&" + constants.WECHAT.REDIRECT_RUI_KEY + NetworkUtils.encode(window.location.href) + "&" + constants.WECHAT.RESPONSE_TYPE + "&" + constants.WECHAT.SCOPE + constants.WECHAT.FIN; - console.log("To visit wechat auth addr: " + url); - window.location.href = url; - }, - - initWxSdk() { - const selfPlayer = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); - const origUrl = window.location.protocol + "//" + window.location.host + window.location.pathname; - /* - * The `shareLink` must - * - have its 2nd-order-domain registered as trusted 2nd-order under the targetd `res.jsConfig.app_id`, and - * - extracted from current window.location.href. - */ - const shareLink = origUrl; - const updateAppMsgShareDataObj = { - type: 'link', // 分享类型,music、video或link,不填默认为link - dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 - title: document.title, // 分享标题 - desc: 'Let\'s play together!', // 分享描述 - link: shareLink + (cc.sys.localStorage.getItem('boundRoomId') ? "" : ("?expectedRoomId=" + cc.sys.localStorage.getItem('boundRoomId'))), - imgUrl: origUrl + "/favicon.ico", // 分享图标 - success: function() { - // 设置成功 - } - }; - const menuShareTimelineObj = { - title: document.title, // 分享标题 - link: shareLink + (cc.sys.localStorage.getItem('boundRoomId') ? "" : ("?expectedRoomId=" + cc.sys.localStorage.getItem('boundRoomId'))), - imgUrl: origUrl + "/favicon.ico", // 分享图标 - success: function() {} - }; - - const wxConfigUrl = (window.isUsingWebkitWechatKernel() ? window.atFirstLocationHref : window.location.href); - //接入微信登录接口 - NetworkUtils.ajax({ - "url": backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.WECHAT + constants.ROUTE_PATH.JSCONFIG, - type: "POST", - data: { - "url": wxConfigUrl, - "intAuthToken": selfPlayer.intAuthToken, - }, - success: function(res) { - if (constants.RET_CODE.OK != res.ret) { - console.log("cannot get the wsConfig. retCode == " + res.ret); - return; - } - const jsConfig = res.jsConfig; - console.log(updateAppMsgShareDataObj); - const configData = { - debug: CC_DEBUG, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 - appId: jsConfig.app_id, // 必填,公众号的唯一标识 - timestamp: jsConfig.timestamp.toString(), // 必填,生成签名的时间戳 - nonceStr: jsConfig.nonce_str, // 必填,生成签名的随机串 - jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline'], - signature: jsConfig.signature, // 必填,签名 - }; - console.log("config url: " + wxConfigUrl); - console.log("wx.config: "); - console.log(configData); - wx.config(configData); - console.log("Current window.location.href: " + window.location.href); - wx.ready(function() { - console.log("Here is wx.ready.") - wx.onMenuShareAppMessage(updateAppMsgShareDataObj); - wx.onMenuShareTimeline(menuShareTimelineObj); - }); - wx.error(function(res) { - console.error("wx config fails and error is " + JSON.stringify(res)); - }); - }, - error: function(xhr, status, errMsg) { - console.log("cannot get the wsConfig. errMsg == " + errMsg); - }, - }); - }, }); diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 245ab8a..482c8ef 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -2,6 +2,7 @@ const i18n = require('LanguageData'); i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field const collisions = require('./modules/Collisions'); +const RingBuffer = require('./RingBuffer'); window.ALL_MAP_STATES = { VISUAL: 0, // For free dragging & zooming. @@ -17,9 +18,11 @@ window.ALL_BATTLE_STATES = { }; window.MAGIC_ROOM_DOWNSYNC_FRAME_ID = { - BATTLE_READY_TO_START: -99, PLAYER_ADDED_AND_ACKED: -98, PLAYER_READDED_AND_ACKED: -97, + + BATTLE_READY_TO_START: -1, + BATTLE_START: 0, }; cc.Class({ @@ -121,8 +124,9 @@ cc.Class({ type: cc.Float, default: 1.0/60 }, - rollbackInMainUpdate: { - default: true + maxChasingRenderFramesPerUpdate: { + type: cc.Integer, + default: 5 }, }, @@ -130,27 +134,39 @@ cc.Class({ return (0 == inputFrameId%10); }, - _dumpToFullFrameCache: function(fullFrame) { + _dumpToRenderCache: function(roomDownsyncFrame) { const self = this; - self.recentFrameCache.set(fullFrame.id, fullFrame); - if (fullFrame.id >= self.recentFrameCacheEd) { - // Should be consecutive - ++self.recentFrameCacheEd; + const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId; + while (self.recentInputCache.stFrameId < minToKeepRenderFrameId) { + self.recentRenderCache.pop(); } - while (self.recentFrameCacheEd - self.recentFrameCacheSt >= self.recentFrameCacheMaxCount) { - self.recentFrameCache.delete(self.recentFrameCacheSt++); + const existing = self.recentRenderCache.getByFrameId(roomDownsyncFrame.id); + if (null != existing) { + existing.players = roomDownsyncFrame.players; + existing.sentAt = roomDownsyncFrame.sentAt; + existing.countdownNanos = roomDownsyncFrame.countdownNanos; + existing.treasures = roomDownsyncFrame.treasures; + existing.bullets = roomDownsyncFrame.bullets; + existing.speedShoes = roomDownsyncFrame.speedShoes; + existing.guardTowers = roomDownsyncFrame.guardTowers; + existing.playerMetas = roomDownsyncFrame.playerMetas; + } else { + self.recentRenderCache.put(roomDownsyncFrame); } }, _dumpToInputCache: function(inputFrameDownsync) { const self = this; - self.recentInputCache.set(inputFrameDownsync.inputFrameId, inputFrameDownsync); - if (inputFrameDownsync.inputFrameId >= self.recentInputCacheEd) { - // Should be consecutive - ++self.recentInputCacheEd; + const minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction. + while (self.recentInputCache.stFrameId < minToKeepInputFrameId) { + self.recentInputCache.pop(); } - while (self.recentInputCacheEd - self.recentInputCacheSt >= self.recentInputCacheMaxCount) { - self.recentInputCache.delete(self.recentInputCacheSt++); + const existing = self.recentInputCache.getByFrameId(inputFrameDownsync.inputFrameId); + if (null != existing) { + existing.inputList = inputFrameDownsync.inputList; + existing.confirmedList = inputFrameDownsync.confirmedList; + } else { + self.recentInputCache.put(inputFrameDownsync); } }, @@ -164,47 +180,40 @@ cc.Class({ }, _shouldGenerateInputFrameUpsync(renderFrameId) { - return ((renderFrameId & 3) == 0); // 3 is 0x0011 + return ((renderFrameId & ((1 << this.inputScaleFrames)-1)) == 0); }, + _allConfirmed(confirmedList) { + return (confirmedList+1) == (1 << this.playerRichInfoDict.size); + }, + _generateInputFrameUpsync(inputFrameId) { - const instance = this; + const self = this; if ( - null == instance.ctrl || - null == instance.selfPlayerInfo + null == self.ctrl || + null == self.selfPlayerInfo ) { return [null, null]; } - const discreteDir = instance.ctrl.getDiscretizedDirection(); - let prefabbedInputList = null; - let selfPlayerLastInputFrameInput = 0; - if (0 == instance.recentInputCacheEd) { - prefabbedInputList = new Array(instance.playerRichInfoDict.size).fill(0); - } else { - if (!instance.recentInputCache.has(instance.recentInputCacheEd-1)) { - console.warn("_generateInputFrameUpsync: recentInputCache is NOT having inputFrameId=", instance.recentInputCacheEd-1, "; recentInputCache=", instance._stringifyRecentInputCache(false)); - prefabbedInputList = new Array(instance.playerRichInfoDict.size).fill(0); - } else { - prefabbedInputList = Array.from(instance.recentInputCache.get(instance.recentInputCacheEd-1).inputList); - selfPlayerLastInputFrameInput = prefabbedInputList[(instance.selfPlayerInfo.joinIndex-1)]; // it's an integer, thus making a copy here, not impacted by later assignments - } - } - - prefabbedInputList[(instance.selfPlayerInfo.joinIndex-1)] = discreteDir.encodedIdx; - - const prefabbedInputFrameDownsync = { - inputFrameId: inputFrameId, - inputList: prefabbedInputList, - confirmedList: (1 << (instance.selfPlayerInfo.joinIndex-1)) + const joinIndex = self.selfPlayerInfo.joinIndex; + const discreteDir = self.ctrl.getDiscretizedDirection(); + const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId); + const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice()); + prefabbedInputList[(joinIndex-1)] = discreteDir.encodedIdx; + const prefabbedInputFrameDownsync = { + inputFrameId: inputFrameId, + inputList: prefabbedInputList, + confirmedList: (1 << (self.selfPlayerInfo.joinIndex-1)) }; - instance._dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames" + self._dumpToInputCache(prefabbedInputFrameDownsync); // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames" - return [selfPlayerLastInputFrameInput, discreteDir.encodedIdx]; + const previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex-1]); + return [previousSelfInput, discreteDir.encodedIdx]; }, - _shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, lastUpsyncInputFrameId, currInputFrameId) { + shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, lastUpsyncInputFrameId, currInputFrameId) { /* For a 2-player-battle, this "shouldUpsyncForEarlyAllConfirmedOnServer" can be omitted, however for more players in a same battle, to avoid a "long time non-moving player" jamming the downsync of other moving players, we should use this flag. */ @@ -213,33 +222,33 @@ cc.Class({ return shouldUpsyncForEarlyAllConfirmedOnServer || (prevSelfInput != currSelfInput); }, - _sendInputFrameUpsyncBatch(inputFrameId) { + sendInputFrameUpsyncBatch(inputFrameId) { // [WARNING] Why not just send the latest input? Because different player would have a different "inputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"! - const instance = this; + const self = this; let inputFrameUpsyncBatch = []; - for (let i = instance.lastUpsyncInputFrameId+1; i <= inputFrameId; ++i) { - const inputFrameDownsync = instance.recentInputCache.get(i); - if (null == inputFrameDownsync) { - console.warn("_sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, "; recentInputCache=", instance._stringifyRecentInputCache(false)); - } else { - const inputFrameUpsync = { - inputFrameId: i, - encodedDir: inputFrameDownsync.inputList[instance.selfPlayerInfo.joinIndex-1], - }; - inputFrameUpsyncBatch.push(inputFrameUpsync); - } + for (let i = self.lastUpsyncInputFrameId+1; i <= inputFrameId; ++i) { + const inputFrameDownsync = self.recentInputCache.getByFrameId(i); + if (null == inputFrameDownsync) { + console.warn("sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=", i, "; recentInputCache=", self._stringifyRecentInputCache(false)); + } else { + const inputFrameUpsync = { + inputFrameId: i, + encodedDir: inputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex-1], + }; + inputFrameUpsyncBatch.push(inputFrameUpsync); + } } const reqData = window.WsReq.encode({ msgId: Date.now(), - playerId: instance.selfPlayerInfo.id, + playerId: self.selfPlayerInfo.id, act: window.UPSYNC_MSG_ACT_PLAYER_CMD, - joinIndex: instance.selfPlayerInfo.joinIndex, - ackingFrameId: instance.lastRoomDownsyncFrameId, - ackingInputFrameId: instance.lastDownsyncInputFrameId, + joinIndex: self.selfPlayerInfo.joinIndex, + ackingFrameId: self.lastAllConfirmedRenderFrameId, + ackingInputFrameId: self.lastAllConfirmedInputFrameId, inputFrameUpsyncBatch: inputFrameUpsyncBatch, }).finish(); window.sendSafely(reqData); - instance.lastUpsyncInputFrameId = inputFrameId; + self.lastUpsyncInputFrameId = inputFrameId; }, onEnable() { @@ -268,9 +277,6 @@ cc.Class({ if (null != window.handleClientSessionCloseOrError) { window.handleClientSessionCloseOrError = null; } - if (self.inputControlTimer) { - clearInterval(self.inputControlTimer); - } }, popupSimplePressToGo(labelString, hideYesButton) { @@ -317,6 +323,7 @@ cc.Class({ self.countdownNanos = null; // Clearing previous info of all players. [BEGINS] + self.collisionPlayerIndexPrefix = (1 << 17); // For tracking the movements of players if (null != self.playerRichInfoDict) { self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { if (playerRichInfo.node.parent) { @@ -327,30 +334,27 @@ cc.Class({ self.playerRichInfoDict = new Map(); // Clearing previous info of all players. [ENDS] - self.lastRoomDownsyncFrameId = 0; self.renderFrameId = 0; // After battle started + self.lastAllConfirmedRenderFrameId = -1; + self.lastAllConfirmedInputFrameId = -1; + self.chaserRenderFrameId = -1; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "handleInputFrameDownsyncBatch" + self.inputDelayFrames = 8; self.inputScaleFrames = 2; - self.lastDownsyncInputFrameId = -1; - self.lastAllConfirmedInputFrameId = -1; self.lastUpsyncInputFrameId = -1; self.inputFrameUpsyncDelayTolerance = 2; - self.recentFrameCache = new Map(); - self.recentFrameCacheSt = 0; // closed index - self.recentFrameCacheEd = 0; // open index - self.recentFrameCacheMaxCount = 1024; + self.recentRenderCache = new RingBuffer(1024); self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". - self.recentInputCache = new Map(); // TODO: Use a ringbuf instead - self.recentInputCacheSt = 0; // closed index - self.recentInputCacheEd = 0; // open index - self.recentInputCacheMaxCount = 1024; - self.toRollbackRenderFrameId1 = null; - self.toRollbackInputFrameId1 = null; + self.recentInputCache = new RingBuffer(1024); self.latestCollisionSys = new collisions.Collisions(); self.chaserCollisionSys = new collisions.Collisions(); + + self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used + self.latestCollisionSysMap = new Map(); + self.chaserCollisionSysMap = new Map(); self.transitToState(ALL_MAP_STATES.VISUAL); @@ -439,8 +443,6 @@ cc.Class({ self.clientUpsyncFps = 60; window.handleBattleColliderInfo = function(parsedBattleColliderInfo) { - console.log(parsedBattleColliderInfo); - self.battleColliderInfo = parsedBattleColliderInfo; const tiledMapIns = self.node.getComponent(cc.TiledMap); @@ -455,11 +457,8 @@ cc.Class({ [WARNING] - The order of the following statements is important, because we should have finished "_resetCurrentMatch" before the first "RoomDownsyncFrame". - - It's important to assign new "tmxAsset" before "extractBoundaryObjects => initMapNodeByTiledBoundaries", to ensure that the correct tilesets are used. + - It's important to assign new "tmxAsset" before "extractBoundaryObjects", to ensure that the correct tilesets are used. - To ensure clearance, put destruction of the "cc.TiledMap" component preceding that of "mapNode.destroyAllChildren()". - - -- YFLu, 2019-09-07 - */ tiledMapIns.tmxAsset = null; @@ -481,9 +480,22 @@ cc.Class({ singleImageLayer.node.opacity = 0; } + let barrierIdCounter = 0; const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); - tileCollisionManager.initMapNodeByTiledBoundaries(self, mapNode, boundaryObjs); - + for (let boundaryObj of boundaryObjs.barriers) { + const x0 = boundaryObj[0].x, y0 = boundaryObj[0].y; + let pts = []; + // TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent. + for (let i = 0; i < boundaryObj.length; ++i) { + pts.push([boundaryObj[i].x-x0, boundaryObj[i].y-y0]); + } + const newBarrierLatest = self.latestCollisionSys.createPolygon(x0, y0, pts); + const newBarrierChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts); + ++barrierIdCounter; + const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); + self.latestCollisionSysMap.set(collisionBarrierIndex, newBarrierLatest); + self.chaserCollisionSysMap.set(collisionBarrierIndex, newBarrierChaser); + } self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); Object.assign(self.selfPlayerInfo, { @@ -530,13 +542,6 @@ cc.Class({ // Right upon establishment of the "PersistentSessionClient", we should receive an initial signal "BattleColliderInfo" earlier than any "RoomDownsyncFrame" containing "PlayerMeta" data. const refFrameId = rdf.refFrameId; switch (refFrameId) { - case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_READY_TO_START: - // 显示倒计时 - self.playersMatched(rdf.playerMetas); - // 隐藏返回按钮 - findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); - findingPlayerScriptIns.hideExitButton(); - return; case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_ADDED_AND_ACKED: self._initPlayerRichInfoDict(rdf.players, rdf.playerMetas); // 显示匹配玩家 @@ -561,20 +566,20 @@ cc.Class({ } } return; + case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_READY_TO_START: + // 显示倒计时 + self.playersMatched(rdf.playerMetas); + // 隐藏返回按钮 + findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); + findingPlayerScriptIns.hideExitButton(); + return; + case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START: + self.onBattleStarted(rdf); + return; } const frameId = rdf.id; - if (0 == self.lastRoomDownsyncFrameId) { - if (1 == frameId) { - // No need to prompt upon rejoined. - self.popupSimplePressToGo(i18n.t("gameTip.start")); - } - - self.onBattleStarted(rdf); - } - - self._dumpToFullFrameCache(rdf); - self.lastRoomDownsyncFrameId = frameId; + self.lastAllConfirmedRenderFrameId = frameId; // TODO: Inject a NetworkDoctor as introduced in https://app.yinxiang.com/shard/s61/nl/13267014/5c575124-01db-419b-9c02-ec81f78c6ddc/. }; @@ -584,13 +589,13 @@ cc.Class({ return; } - // console.log("Received inputFrameDownsyncBatch=", batch, ", now correspondingLastLocalInputFrame=", self.recentInputCache.get(batch[batch.length-1].inputFrameId)); + // console.log("Received inputFrameDownsyncBatch=", batch, ", now correspondingLastLocalInputFrame=", self.recentInputCache.getByFrameId(batch[batch.length-1].inputFrameId)); let firstPredictedYetIncorrectInputFrameId = null; let firstPredictedYetIncorrectInputFrameJoinIndex = null; for (let k in batch) { const inputFrameDownsync = batch[k]; const inputFrameDownsyncId = inputFrameDownsync.inputFrameId; - const localInputFrame = self.recentInputCache.get(inputFrameDownsyncId); + const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId); if (null == localInputFrame) { console.warn("handleInputFrameDownsyncBatch: recentInputCache is NOT having inputFrameDownsyncId=", inputFrameDownsyncId, "; now recentInputCache=", self._stringifyRecentInputCache(false)); } else { @@ -604,10 +609,8 @@ cc.Class({ } } } + self.lastAllConfirmedInputFrameId = inputFrameDownsyncId; self._dumpToInputCache(inputFrameDownsync); - // [WARNING] Currently "lastDownsyncInputFrameId" and "lastAllConfirmedInputFrameId" are identical, but they (their definitions) are prone to changes in the future - self.lastDownsyncInputFrameId = inputFrameDownsyncId; - self.lastAllConfirmedInputFrameId = inputFrameDownsyncId; // TODO: Should localInputFrameId > self.lastAllConfirmedInputFrameId be corrected for their predictions on the other players? } if (null != firstPredictedYetIncorrectInputFrameId) { @@ -616,21 +619,24 @@ cc.Class({ const inputFrameId1 = firstPredictedYetIncorrectInputFrameId; const renderFrameId1 = self._convertToRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId" if (renderFrameId1 < renderFrameId2) { - // No need to rollback when "renderFrameId1 == renderFrameId2", because the "delayedInputFrame for renderFrameId2" is not yet executed by now, it just went through "++self.renderFrameId" in "update(dt)" and js-runtime is mostly single-threaded in our programmable range. - console.warn("Mismatched input detected!: [inputFrameId1:", inputFrameId1, ", inputFrameId2:", inputFrameId2, "), [renderFrameId1:", renderFrameId1, ", renderFrameId2:", renderFrameId2, "). "); - if (true == self.rollbackInMainUpdate) { - // The actual rollback-and-replay would later be executed in update(dt). - if (null == self.toRollbackRenderFrameId1) { - self.toRollbackRenderFrameId1 = renderFrameId1; - self.toRollbackInputFrameId1 = inputFrameId1; - } else { - // Deliberately left blank. This case merely extends the ending indices - } - } else { - self._rollbackAndReplay(inputFrameId1, renderFrameId1, inputFrameId2, renderFrameId2); - } + /* + A typical case is as follows. + -------------------------------------------------------- + [lastAllConfirmedRenderFrameId] : 22 + : 36 + + + [chaserRenderFrameId] : 62 + + : 64 + -------------------------------------------------------- + */ + console.warn("Mismatched input detected!: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1); + // The actual rollback-and-replay would later be executed in update(dt). + self.chaserRenderFrameId = renderFrameId1; } else { + // No need to rollback when "renderFrameId1 == renderFrameId2", because the "delayedInputFrame for renderFrameId2" is not yet executed by now, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range. console.log("Mismatched input yet no rollback needed: [inputFrameId1:", inputFrameId1, ", inputFrameId2:", inputFrameId2, "), [renderFrameId1:", renderFrameId1, ", renderFrameId2:", renderFrameId2, "). "); } } @@ -708,15 +714,16 @@ cc.Class({ self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode); } self.transitToState(ALL_MAP_STATES.VISUAL); - - self._applyRoomDownsyncFrameDynamics(rdf); + self.chaserRenderFrameId = MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START; + self.applyRoomDownsyncFrameDynamics(rdf); + self._dumpToRenderCache(rdf); self.battleState = ALL_BATTLE_STATES.IN_BATTLE; // Starts the increment of "self.renderFrameId" in "self.update(dt)" }, logBattleStats() { const self = this; let s = []; - s.push("Battle stats: lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastDownsyncInputFrameId=" + self.lastDownsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId); + s.push("Battle stats: lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId); self.recentInputCache.forEach((inputFrameDownsync, inputFrameId) => { s.push(JSON.stringify(inputFrameDownsync)); @@ -745,12 +752,19 @@ cc.Class({ }, spawnPlayerNode(joinIndex, x, y) { - const instance = this; - const newPlayerNode = 1 == joinIndex ? cc.instantiate(instance.player1Prefab) : cc.instantiate(instance.player2Prefab); // hardcoded for now, car color determined solely by joinIndex + const self = this; + const newPlayerNode = 1 == joinIndex ? cc.instantiate(self.player1Prefab) : cc.instantiate(self.player2Prefab); // hardcoded for now, car color determined solely by joinIndex newPlayerNode.setPosition(cc.v2(x, y)); - newPlayerNode.getComponent("SelfPlayer").mapNode = instance.node; + newPlayerNode.getComponent("SelfPlayer").mapNode = self.node; + const currentSelfColliderCircle = newPlayerNode.getComponent(cc.CircleCollider); - safelyAddChild(instance.node, newPlayerNode); + const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle); + const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle); + const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; + self.latestCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderLatest); + self.chaserCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderChaser); + + safelyAddChild(self.node, newPlayerNode); setLocalZOrder(newPlayerNode, 5); newPlayerNode.active = true; @@ -771,35 +785,28 @@ cc.Class({ prevSelfInput = prevAndCurrInputs[0]; currSelfInput = prevAndCurrInputs[1]; } - if (self._shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) { - // TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously? - self._sendInputFrameUpsyncBatch(noDelayInputFrameId); - } - - const delayedInputFrameId = self._convertToInputFrameId(self.renderFrameId, self.inputDelayFrames); // The "inputFrameId" to use at current "renderFrameId" - if (true == self.rollbackInMainUpdate) { - if (null != self.toRollbackRenderFrameId1) { - // Rollback-and-replay if necessary, prior to applying the latest dynamics - const anotherJoinIndex = 3-self.selfPlayerInfo.joinIndex; - self._rollbackAndReplay(self.toRollbackInputFrameId1, self.toRollbackRenderFrameId1, delayedInputFrameId, self.renderFrameId); - self.toRollbackRenderFrameId1 = null; - self.toRollbackRenderFrameId2 = null; - } - } - const delayedInputFrameDownsync = self.assembleInputFrameDownsync(delayedInputFrameId); - if (null == delayedInputFrameDownsync) { - console.warn("update(dt): recentInputCache is NOT having inputFrameId=", delayedInputFrameId, "; recentInputCache=", self._stringifyRecentInputCache(false)); - } else { - self._applyInputFrameDownsyncDynamics(delayedInputFrameDownsync, false); - } - const rdf = self._createRoomDownsyncFrameLocally(); - self._dumpToFullFrameCache(rdf); - /* - if (null != delayedInputFrameDownsync && null != delayedInputFrameDownsync.inputList && 0 < delayedInputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex-1]) { - console.log("My critical status: renderFrame=", JSON.stringify(rdf), ", delayedInputFrameDownsync=", JSON.stringify(delayedInputFrameDownsync)); + let t0 = performance.now(); + if (self.shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) { + // TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously? + self.sendInputFrameUpsyncBatch(noDelayInputFrameId); + } + + let t1 = performance.now(); + if (self.chaserRenderFrameId <= self.renderFrameId) { + let chaserUpperRenderFrameId = (self.chaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); + if (chaserUpperRenderFrameId > self.renderFrameId) chaserUpperRenderFrameId = self.renderFrameId; + self.rollbackAndReplay(self.chaserRenderFrameId, chaserUpperRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); + self.chaserRenderFrameId = chaserUpperRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! } - */ + let t2 = performance.now(); + + // Inside "self.rollbackAndReplay", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. + const rdf = self.rollbackAndReplay(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); + + self.applyRoomDownsyncFrameDynamics(rdf); + let t3 = performance.now(); + ++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!! } const mapNode = self.node; @@ -827,7 +834,7 @@ cc.Class({ console.error("Error during Map.update", err); } if (null != self.ctrl) { - self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); + self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); } }, @@ -923,31 +930,59 @@ cc.Class({ }, 2000); }, - _createRoomDownsyncFrameLocally() { + _createRoomDownsyncFrameLocally(renderFrameId, collisionSys, collisionSysMap) { const self = this; + const prevRenderFrameId = renderFrameId-1; + const inputFrameForPrevRenderFrame = ( + 0 > prevRenderFrameId + ? + null + : + self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(prevRenderFrameId, self.inputDelayFrames)) + ); + + // TODO: Find a better way to assign speeds instead of using "speedRefRenderFrameId". + const speedRefRenderFrameId = prevRenderFrameId; + const speedRefRenderFrame = ( + 0 > prevRenderFrameId + ? + null + : + self.recentRenderCache.getByFrameId(prevRenderFrameId) + ); + const rdf = { - id: self.renderFrameId, - refFrameId: self.renderFrameId, - players: {}, - countdownNanos: self.countdownNanos + id: renderFrameId, + refFrameId: renderFrameId, + players: {} }; self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { const joinIndex = playerRichInfo.joinIndex; - const playerNode = playerRichInfo.node; - const playerScriptIns = playerRichInfo.scriptIns; + const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; + const playerCollider = collisionSysMap.get(collisionPlayerIndex); rdf.players[playerRichInfo.id] = { id: playerRichInfo.id, - x: playerNode.position.x, - y: playerNode.position.y, - dir: playerScriptIns.activeDirection, - speed: playerScriptIns.speed, + x: playerCollider.x, + y: playerCollider.y, + dir: self.ctrl.decodeDirection(null == inputFrameForPrevRenderFrame ? 0 : inputFrameForPrevRenderFrame.inputList[joinIndex-1]), + speed: (null == speedRefRenderFrame ? playerRichInfo.speed : speedRefRenderFrame.players[playerRichInfo.id].speed), joinIndex: joinIndex }; }); + if ( + null != inputFrameForPrevRenderFrame && self._allConfirmed(inputFrameForPrevRenderFrame.confirmedList) + && + self.lastAllConfirmedRenderFrameId >= prevRenderFrameId + && + rdf.id > self.lastAllConfirmedRenderFrameId + ) { + self.lastAllConfirmedRenderFrameId = rdf.id; + } + self._dumpToRenderCache(rdf); return rdf; }, - _applyRoomDownsyncFrameDynamics(rdf) { + applyRoomDownsyncFrameDynamics(rdf) { const self = this; self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { @@ -958,11 +993,11 @@ cc.Class({ }); }, - assembleInputFrameDownsync(inputFrameId) { + getCachedInputFrameDownsyncWithPrediction(inputFrameId) { const self = this; - let inputFrameDownsync = self.recentInputCache.get(inputFrameId); + let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); if (-1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) { - const lastAllConfirmedInputFrame = self.recentInputCache.get(self.lastAllConfirmedInputFrameId); + const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { if (i == self.selfPlayerInfo.joinIndex-1) continue; inputFrameDownsync.inputList[i] = lastAllConfirmedInputFrame.inputList[i]; @@ -972,49 +1007,64 @@ cc.Class({ return inputFrameDownsync; }, - _applyInputFrameDownsyncDynamics(inputFrameDownsync, invokeUpdateToo) { - // This application DOESN'T use a "full physics engine", but only "collider detection" of "box2d", thus when resetting room state, there's no need of resetting "momentums". - const self = this; - const inputs = inputFrameDownsync.inputList; - // Update controlled player nodes - self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { - const joinIndex = playerRichInfo.joinIndex; - const playerScriptIns = playerRichInfo.scriptIns; - const decodedInput = self.ctrl.decodeDirection(inputs[joinIndex-1]); - playerScriptIns.scheduleNewDirection(decodedInput, true); - if (invokeUpdateToo) { - playerScriptIns.update(self.rollbackEstimatedDt); - } - }); - - if (invokeUpdateToo) { - // [WARNING] CocosCreator v2.2.1 uses a singleton "CCDirector" to schedule "tree descendent updates" and "collision detections" in different timers, thus the following manual trigger of collision detection might not produce the same outcome for the "selfPlayer" as the other peers. Moreover, the aforementioned use of different timers is an intrinsic source of error! - - cc.director._collisionManager.update(self.rollbackEstimatedDt); // Just to avoid unexpected wall penetration, no guarantee on determinism - } - }, - - _rollbackAndReplay(inputFrameId1, renderFrameId1, inputFrameId2, renderFrameId2) { - const self = this; - const rdf1 = self.recentFrameCache.get(renderFrameId1); - if (null == rdf1) { - console.error("renderFrameId1=", renderFrameId1, "doesn't exist in recentFrameCache ", self._stringifyRecentFrameCache(false), ": COULDN'T ROLLBACK!"); + rollbackAndReplay(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { + if (renderFrameSt == renderFrameIdEd) { + console.warn("Unexpected input!"); return; } - let t0 = performance.now(); - self._applyRoomDownsyncFrameDynamics(rdf1); - // DON'T apply inputFrameDownsync dynamics for exactly "renderFrameId2", see the comment around the invocation of "_rollbackAndReplay". - for (let renderFrameId = renderFrameId1; renderFrameId < renderFrameId2; ++renderFrameId) { - const delayedInputFrameId = self._convertToInputFrameId(renderFrameId, self.inputDelayFrames); - const delayedInputFrameDownsync = self.assembleInputFrameDownsync(delayedInputFrameId); - self._applyInputFrameDownsyncDynamics(delayedInputFrameDownsync, true); - // console.log("_rollbackAndReplay, AFTER:", self._stringifyRollbackResult(renderFrameId, delayedInputFrameDownsync)); - } - let t1 = performance.now(); - console.log("Executed rollback-and-replay: [inputFrameId1:", inputFrameId1, ", inputFrameId2:", inputFrameId2, "), [renderFrameId1:", renderFrameId1, ", renderFrameId2:", renderFrameId2, "). It took", t1-t0, "milliseconds"); - }, + const self = this; + const renderFrameSt = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame" + /* + Reset "position" of players in "collisionSys" according to "renderFrameSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics. + */ + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + const joinIndex = playerRichInfo.joinIndex; + const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; + const playerCollider = collisionSysMap.get(collisionPlayerIndex); + const player = renderFrameSt.players[playerId]; + playerCollider.x = player.x; + playerCollider.y = player.y; + }); + const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle? + /* + This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd". + */ + for (let i = renderFrameSt; i < renderFrameIdEd; ++i) { + const renderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame" + const j = self._convertToInputFrameId(i, self.inputDelayFrames); + const inputs = self.recentInputCache.getByFrameId(j).inputList; + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + const joinIndex = playerRichInfo.joinIndex; + const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; + const playerCollider = collisionSysMap.get(collisionPlayerIndex); + const player = renderFrame.players[playerId]; + const decodedInput = self.ctrl.decodeDirection(inputs[joinIndex-1]); + playerCollider.x += player.speed*decodedInput.dx*self.rollbackEstimatedDt; + playerCollider.y += player.speed*decodedInput.dy*self.rollbackEstimatedDt; + }); + + collisionSys.update(); + + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + const joinIndex = playerRichInfo.joinIndex; + const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; + const playerCollider = collisionSysMap.get(collisionPlayerIndex); + const potentials = playerCollider.potentials(); + for (const barrier of potentials) { + // Test if the player collides with the wall + if (!playerCollider.collides(barrier, result)) continue; + // Push the player out of the wall + playerCollider.x -= result.overlap * result.overlap_x; + playerCollider.y -= result.overlap * result.overlap_y; + } + }); + } + + return self._createRoomDownsyncFrameLocally(renderFrameIdEd, collisionSys, collisionSysMap); + }, + _initPlayerRichInfoDict(players, playerMetas) { const self = this; for (let k in players) { @@ -1037,18 +1087,6 @@ cc.Class({ } }, - _stringifyRecentFrameCache(usefullOutput) { - if (true == usefullOutput) { - let s = []; - self.recentFrameCache.forEach((roomDownsyncFrame, renderFrameId) => { - s.push(JSON.stringify(roomDownsyncFrame)); - }); - - return s.join('\n'); - } - return "[stRenderFrameId=" + self.recentFrameCacheSt + ", edRenderFrameId=" + self.recentFrameCacheEd + ")"; - }, - _stringifyRecentInputCache(usefullOutput) { if (true == usefullOutput) { let s = []; @@ -1062,7 +1100,6 @@ cc.Class({ }, _stringifyRollbackResult(renderFrameId, delayedInputFrameDownsync) { - // Slightly different from "_createRoomDownsyncFrameLocally" const self = this; const s = ( null == delayedInputFrameDownsync @@ -1078,7 +1115,6 @@ cc.Class({ delayedInputFrameDownsync: delayedInputFrameDownsync, } ); - let players = {}; self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { const joinIndex = playerRichInfo.joinIndex; const playerNode = playerRichInfo.node; diff --git a/frontend/assets/scripts/RingBuffer.js b/frontend/assets/scripts/RingBuffer.js new file mode 100644 index 0000000..6262c6d --- /dev/null +++ b/frontend/assets/scripts/RingBuffer.js @@ -0,0 +1,63 @@ +var RingBuffer = function(capacity) { + this.ed = 0; // write index, open index + this.st = 0; // read index, closed index + this.edFrameId = 0; + this.stFrameId = 0; + this.n = capacity; + this.cnt = 0; // the count of valid elements in the buffer, used mainly to distinguish what "st == ed" means for "Pop" and "Get" methods + this.eles = new Array(capacity).fill(null); +}; + +RingBuffer.prototype.put = function(item) { + this.eles[this.ed] = item + this.edFrameId++; + this.cnt++; + this.ed++; + if (this.ed >= this.n) { + this.ed -= this.n; // Deliberately not using "%" operator for performance concern + } +}; + +RingBuffer.prototype.pop = function() { + if (0 == this.cnt) { + return null; + } + const item = this.eles[this.st]; + this.stFrameId++; + this.cnt--; + this.st++; + if (this.st >= this.n) { + this.st -= this.n; + } + return item; +}; + +RingBuffer.prototype.getByOffset = function(offsetFromSt) { + if (0 == this.cnt) { + return null; + } + let arrIdx = this.st + offsetFromSt; + if (this.st < this.ed) { + // case#1: 0...st...ed...n-1 + if (this.st <= arrIdx && arrIdx < this.ed) { + return this.eles[arrIdx]; + } + } else { + // if this.st >= this.sd + // case#2: 0...ed...st...n-1 + if (arrIdx >= this.n) { + arrIdx -= this.n + } + if (arrIdx >= this.st || arrIdx < this.ed) { + return this.eles[arrIdx]; + } + } + + return null; +}; + +RingBuffer.prototype.getByFrameId = function(frameId) { + return this.getByOffset(frameId - this.stFrameId); +}; + +module.exports = RingBuffer; diff --git a/frontend/assets/scripts/WechatGameLogin.js.meta b/frontend/assets/scripts/RingBuffer.js.meta similarity index 74% rename from frontend/assets/scripts/WechatGameLogin.js.meta rename to frontend/assets/scripts/RingBuffer.js.meta index bf132d7..4c931a3 100644 --- a/frontend/assets/scripts/WechatGameLogin.js.meta +++ b/frontend/assets/scripts/RingBuffer.js.meta @@ -1,6 +1,6 @@ { "ver": "1.0.5", - "uuid": "8264fb72-e348-45e4-9ab3-5bffb9a561ee", + "uuid": "9ec706f0-811c-403b-93a7-b34a7e5f8068", "isPlugin": false, "loadPluginInWeb": true, "loadPluginInNative": true, diff --git a/frontend/assets/scripts/TileCollisionManagerSingleton.js b/frontend/assets/scripts/TileCollisionManagerSingleton.js index 2fdee4d..a37cd25 100644 --- a/frontend/assets/scripts/TileCollisionManagerSingleton.js +++ b/frontend/assets/scripts/TileCollisionManagerSingleton.js @@ -389,8 +389,6 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo let childrenOfCurrentTile = null; if (cc.sys.isNative) { childrenOfCurrentTile = currentTile.getElementsByTagName("objectgroup"); - } else if (cc.sys.platform == cc.sys.WECHAT_GAME) { - childrenOfCurrentTile = currentTile.childNodes; } else { childrenOfCurrentTile = currentTile.children; } @@ -498,7 +496,7 @@ TileCollisionManager.prototype.extractBoundaryObjects = function (withTiledMapNo let layerDOMTrees = []; const mapDomTree = mapInfo._parser._parseXML(tiledMapIns.tmxAsset.tmxXmlStr).documentElement; - const mapDOMAllChildren = (cc.sys.platform == cc.sys.WECHAT_GAME ? mapDomTree.childNodes : mapDomTree.children); + const mapDOMAllChildren = (mapDomTree.children); for (let mdtIdx = 0; mdtIdx < mapDOMAllChildren.length; ++mdtIdx) { const tmpCh = mapDOMAllChildren[mdtIdx]; if (mapInfo._shouldIgnoreNode(tmpCh)) { @@ -582,6 +580,7 @@ TileCollisionManager.prototype.isOutOfMapNode = function (tiledMapNode, continuo }; TileCollisionManager.prototype.initMapNodeByTiledBoundaries = function(mapScriptIns, mapNode, extractedBoundaryObjs) { + // TODO: TO DEPRECATE! const tiledMapIns = mapNode.getComponent(cc.TiledMap); if (extractedBoundaryObjs.grandBoundaries) { window.grandBoundary = []; diff --git a/frontend/assets/scripts/WechatGameLogin.js b/frontend/assets/scripts/WechatGameLogin.js deleted file mode 100644 index 8e5f849..0000000 --- a/frontend/assets/scripts/WechatGameLogin.js +++ /dev/null @@ -1,628 +0,0 @@ -const i18n = require('LanguageData'); -i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field - -const WECHAT_ON_HIDE_TARGET_ACTION = { - SHARE_CHAT_MESSAGE: 8, - CLOSE: 3, -}; - -const pbStructRoot = require('./modules/room_downsync_frame_proto_bundle.forcemsg.js'); -window.RoomDownsyncFrame = pbStructRoot.treasurehunterx.RoomDownsyncFrame; -window.BattleColliderInfo = pbStructRoot.treasurehunterx.BattleColliderInfo; - -cc.Class({ - extends: cc.Component, - - properties: { - cavasNode: { - default: null, - type: cc.Node - }, - backgroundNode: { - default: null, - type: cc.Node - }, - loadingPrefab: { - default: null, - type: cc.Prefab - }, - tipsLabel: { - default: null, - type: cc.Label, - }, - - downloadProgress: { - default: null, - type: cc.ProgressBar, - }, - writtenBytes: { - default: null, - type: cc.Label, - }, - expectedToWriteBytes: { - default: null, - type: cc.Label, - }, - - handlerProgress: { - default: null, - type: cc.ProgressBar, - }, - handledUrlsCount: { - default: null, - type: cc.Label, - }, - toHandledUrlsCount: { - default: null, - type: cc.Label, - }, - }, - - - // LIFE-CYCLE CALLBACKS: - onLoad() { - wx.onShow((res) => { - console.log("+++++ wx onShow(), onShow.res ", res); - window.expectedRoomId = res.query.expectedRoomId; - }); - wx.onHide((res) => { - // Reference https://developers.weixin.qq.com/minigame/dev/api/wx.exitMiniProgram.html. - console.log("+++++ wx onHide(), onHide.res: ", res); - if ( - WECHAT_ON_HIDE_TARGET_ACTION == res.targetAction - || - "back" == res.mode // After "WeChat v7.0.4 on iOS" - || - "close" == res.mode - ) { - window.clearLocalStorageAndBackToLoginScene(); - } else { - // Deliberately left blank. - } - }); - - const self = this; - self.getRetCodeList(); - self.getRegexList(); - - self.showTips(i18n.t("login.tips.AUTO_LOGIN_1")); - self.checkIntAuthTokenExpire().then( - () => { - self.showTips(i18n.t("login.tips.AUTO_LOGIN_2")); - const intAuthToken = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')).intAuthToken; - self.useTokenLogin(intAuthToken); - }, - () => { - // 调用wx.login然后请求登录。 - wx.authorize({ - scope: "scope.userInfo", - success() { - self.showTips(i18n.t("login.tips.WECHAT_AUTHORIZED_AND_INT_AUTH_TOKEN_LOGGING_IN")); - wx.login({ - success(res) { - console.log("wx login success, res: ", res); - const code = res.code; - - wx.getUserInfo({ - success(res) { - const userInfo = res.userInfo; - console.log("Get user info ok: ", userInfo); - self.useWxCodeMiniGameLogin(code, userInfo); - }, - fail(err) { - console.error(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN"), err); - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - self.createAuthorizeThenLoginButton(); - }, - }) - }, - fail(err) { - if (err) { - console.error(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN"), err); - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - self.createAuthorizeThenLoginButton(); - } - }, - }); - }, - fail(err) { - console.error(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN"), err); - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - self.createAuthorizeThenLoginButton(); - } - }) - } - ); - }, - - createAuthorizeThenLoginButton(tips) { - const self = this; - - let sysInfo = wx.getSystemInfoSync(); - //获取微信界面大小 - let width = sysInfo.screenWidth; - let height = sysInfo.screenHeight; - - let button = wx.createUserInfoButton({ - type: 'text', - text: '', - style: { - left: 0, - top: 0, - width: width, - height: height, - backgroundColor: '#00000000', //最后两位为透明度 - color: '#ffffff', - fontSize: 20, - textAlign: "center", - lineHeight: height, - }, - }); - button.onTap((res) => { - console.log(res); - if (null != res.userInfo) { - const userInfo = res.userInfo; - self.showTips(i18n.t("login.tips.WECHAT_AUTHORIZED_AND_INT_AUTH_TOKEN_LOGGING_IN")); - - wx.login({ - success(res) { - console.log('wx.login success, res:', res); - const code = res.code; - self.useWxCodeMiniGameLogin(code, userInfo); - button.destroy(); - }, - fail(err) { - console.err(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN"), err); - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - }, - }); - } else { - self.showTips(i18n.t("login.tips.PLEASE_AUTHORIZE_WECHAT_LOGIN_FIRST")); - } - }) - - }, - - onDestroy() { - console.log("+++++++ WechatGameLogin onDestroy()"); - }, - - showTips(text) { - if (this.tipsLabel != null) { - this.tipsLabel.string = text; - } else { - console.log('Login scene showTips failed') - } - }, - - getRetCodeList() { - const self = this; - self.retCodeDict = constants.RET_CODE; - }, - - getRegexList() { - const self = this; - self.regexList = { - EMAIL: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, - PHONE: /^\+?[0-9]{8,14}$/, - STREET_META: /^.{5,100}$/, - LNG_LAT_TEXT: /^[0-9]+(\.[0-9]{4,6})$/, - SEO_KEYWORD: /^.{2,50}$/, - PASSWORD: /^.{6,50}$/, - SMS_CAPTCHA_CODE: /^[0-9]{4}$/, - ADMIN_HANDLE: /^.{4,50}$/, - }; - }, - - onSMSCaptchaGetButtonClicked(evt) { - var timerEnable = true; - const self = this; - if (!self.checkPhoneNumber('getCaptcha')) { - return; - } - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + - constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.SMS_CAPTCHA + constants.ROUTE_PATH.GET, - type: 'GET', - data: { - phoneCountryCode: self.phoneCountryCodeInput.getComponent(cc.EditBox).string, - phoneNum: self.phoneNumberInput.getComponent(cc.EditBox).string - }, - success: function(res) { - switch (res.ret) { - case constants.RET_CODE.OK: - self.phoneNumberTips.getComponent(cc.Label).string = ''; - self.captchaTips.getComponent(cc.Label).string = ''; - break; - case constants.RET_CODE.DUPLICATED: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.DUPLICATED"); - break; - case constants.RET_CODE.INCORRECT_PHONE_COUNTRY_CODE: - case constants.RET_CODE.INCORRECT_PHONE_NUMBER: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.PHONE_ERR"); - break; - case constants.RET_CODE.IS_TEST_ACC: - self.smsLoginCaptchaInput.getComponent(cc.EditBox).string = res.smsLoginCaptcha; - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.TEST_USER"); - timerEnable = false; - // clearInterval(self.countdownTimer); - break; - case constants.RET_CODE.SMS_CAPTCHA_REQUESTED_TOO_FREQUENTLY: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.SMS_CAPTCHA_FREEQUENT_REQUIRE"); - default: - break; - } - if (timerEnable) - self.countdownTime(self); - } - }); - }, - - countdownTime(self) { - self.smsLoginCaptchaButton.off('click', self.onSMSCaptchaGetButtonClicked); - self.smsLoginCaptchaButton.removeChild(self.smsGetCaptchaNode); - self.smsWaitCountdownNode.parent = self.smsLoginCaptchaButton; - var total = 20; // Magic number - self.countdownTimer = setInterval(function() { - if (total === 0) { - self.smsWaitCountdownNode.parent.removeChild(self.smsWaitCountdownNode); - self.smsGetCaptchaNode.parent = self.smsLoginCaptchaButton; - self.smsWaitCountdownNode.getChildByName('WaitTimeLabel').getComponent(cc.Label).string = 20; - self.smsLoginCaptchaButton.on('click', self.onSMSCaptchaGetButtonClicked); - clearInterval(self.countdownTimer); - } else { - total--; - self.smsWaitCountdownNode.getChildByName('WaitTimeLabel').getComponent(cc.Label).string = total; - } - }, 1000) - - }, - - checkIntAuthTokenExpire() { - return new Promise((resolve, reject) => { - if (!cc.sys.localStorage.getItem("selfPlayer")) { - reject(); - return; - } - const selfPlayer = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); - (selfPlayer.intAuthToken && new Date().getTime() < selfPlayer.expiresAt) ? resolve() : reject(); - }) - }, - - checkPhoneNumber(type) { - const self = this; - const phoneNumberRegexp = self.regexList.PHONE; - var phoneNumberString = self.phoneNumberInput.getComponent(cc.EditBox).string; - if (phoneNumberString) { - return true; - if (!phoneNumberRegexp.test(phoneNumberString)) { - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.PHONE_ERR"); - return false; - } else { - return true; - } - } else { - if (type === 'getCaptcha' || type === 'login') { - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.PHONE_ERR"); - } - return false; - } - }, - - checkCaptcha(type) { - const self = this; - const captchaRegexp = self.regexList.SMS_CAPTCHA_CODE; - var captchaString = self.smsLoginCaptchaInput.getComponent(cc.EditBox).string; - - if (captchaString) { - if (self.smsLoginCaptchaInput.getComponent(cc.EditBox).string.length !== 4 || (!captchaRegexp.test(captchaString))) { - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.CAPTCHA_ERR"); - return false; - } else { - return true; - } - } else { - if (type === 'login') { - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.CAPTCHA_ERR"); - } - return false; - } - }, - - useTokenLogin(_intAuthToken) { - var self = this; - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.INT_AUTH_TOKEN + constants.ROUTE_PATH.LOGIN, - type: "POST", - data: { - intAuthToken: _intAuthToken - }, - success: function(resp) { - self.onLoggedIn(resp); - }, - error: function(xhr, status, errMsg) { - console.log("Login attempt `useTokenLogin` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage() - - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - self.createAuthorizeThenLoginButton(); - }, - timeout: function() { - self.enableInteractiveControls(true); - }, - }); - }, - - enableInteractiveControls(enabled) {}, - - onLoginButtonClicked(evt) { - const self = this; - if (!self.checkPhoneNumber('login') || !self.checkCaptcha('login')) { - return; - } - self.loginParams = { - phoneCountryCode: self.phoneCountryCodeInput.getComponent(cc.EditBox).string, - phoneNum: self.phoneNumberInput.getComponent(cc.EditBox).string, - smsLoginCaptcha: self.smsLoginCaptchaInput.getComponent(cc.EditBox).string - }; - self.enableInteractiveControls(false); - - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + - constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.SMS_CAPTCHA + constants.ROUTE_PATH.LOGIN, - type: "POST", - data: self.loginParams, - success: function(resp) { - self.onLoggedIn(resp); - }, - error: function(xhr, status, errMsg) { - console.log("Login attempt `onLoginButtonClicked` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage() - }, - timeout: function() { - self.enableInteractiveControls(true); - } - }); - }, - onWechatLoggedIn(res) { - const self = this; - if (constants.RET_CODE.OK == res.ret) { - //根据服务器返回信息设置selfPlayer - self.enableInteractiveControls(false); - const date = Number(res.expiresAt); - const selfPlayer = { - expiresAt: date, - playerId: res.playerId, - intAuthToken: res.intAuthToken, - displayName: res.displayName, - avatar: res.avatar, - } - cc.sys.localStorage.setItem('selfPlayer', JSON.stringify(selfPlayer)); - - self.useTokenLogin(res.intAuthToken); - } else { - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - - self.showTips(i18n.t("login.tips.WECHAT_LOGIN_FAILED_TAP_SCREEN_TO_RETRY") + ", errorCode = " + res.ret); - self.createAuthorizeThenLoginButton(); - } - }, - - onLoggedIn(res) { - const self = this; - console.log("OnLoggedIn: ", res); - if (constants.RET_CODE.OK == res.ret) { - if (window.isUsingX5BlinkKernelOrWebkitWeChatKernel()) { - window.initWxSdk = self.initWxSdk.bind(self); - window.initWxSdk(); - } - self.enableInteractiveControls(false); - const date = Number(res.expiresAt); - const selfPlayer = { - expiresAt: date, - playerId: res.playerId, - intAuthToken: res.intAuthToken, - avatar: res.avatar, - displayName: res.displayName, - name: res.name, - } - cc.sys.localStorage.setItem("selfPlayer", JSON.stringify(selfPlayer)); - console.log("cc.sys.localStorage.selfPlayer = ", cc.sys.localStorage.getItem("selfPlayer")); - if (self.countdownTimer) { - clearInterval(self.countdownTimer); - } - - cc.director.loadScene('default_map'); - } else { - console.warn('onLoggedIn failed!') - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.enableInteractiveControls(true); - switch (res.ret) { - case constants.RET_CODE.DUPLICATED: - this.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.DUPLICATED"); - break; - case constants.RET_CODE.TOKEN_EXPIRED: - this.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.LOGIN_TOKEN_EXPIRED"); - break; - case constants.RET_CODE.SMS_CAPTCHA_NOT_MATCH: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.SMS_CAPTCHA_NOT_MATCH"); - break; - case constants.RET_CODE.INCORRECT_CAPTCHA: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.SMS_CAPTCHA_NOT_MATCH"); - break; - case constants.RET_CODE.SMS_CAPTCHA_CODE_NOT_EXISTING: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.SMS_CAPTCHA_NOT_MATCH"); - break; - case constants.RET_CODE.INCORRECT_PHONE_NUMBER: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.INCORRECT_PHONE_NUMBER"); - break; - case constants.RET_CODE.INVALID_REQUEST_PARAM: - self.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.INCORRECT_PHONE_NUMBER"); - break; - case constants.RET_CODE.INCORRECT_PHONE_COUNTRY_CODE: - case constants.RET_CODE.INCORRECT_PHONE_NUMBER: - this.captchaTips.getComponent(cc.Label).string = i18n.t("login.tips.INCORRECT_PHONE_NUMBER"); - break; - default: - break; - } - - self.showTips(i18n.t("login.tips.AUTO_LOGIN_FAILED_WILL_USE_MANUAL_LOGIN")); - self.createAuthorizeThenLoginButton(); - } - }, - - useWXCodeLogin(_code) { - const self = this; - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.WECHAT + constants.ROUTE_PATH.LOGIN, - type: "POST", - data: { - code: _code - }, - success: function(res) { - self.onWechatLoggedIn(res); - }, - error: function(xhr, status, errMsg) { - console.log("Login attempt `onLoginButtonClicked` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.showTips(i18n.t("login.tips.WECHAT_LOGIN_FAILED_TAP_SCREEN_TO_RETRY") + ", errorMsg =" + errMsg); - }, - timeout: function() { - console.log("Login attempt `onLoginButtonClicked` timed out, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.showTips(i18n.t("login.tips.WECHAT_LOGIN_FAILED_TAP_SCREEN_TO_RETRY") + ", errorMsg =" + errMsg); - }, - }); - }, - - // 对比useWxCodeLogin函数只是请求了不同url - useWxCodeMiniGameLogin(_code, _userInfo) { - const self = this; - NetworkUtils.ajax({ - url: backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.WECHATGAME + constants.ROUTE_PATH.LOGIN, - type: "POST", - data: { - code: _code, - avatarUrl: _userInfo.avatarUrl, - nickName: _userInfo.nickName, - }, - success: function(res) { - self.onWechatLoggedIn(res); - }, - error: function(xhr, status, errMsg) { - console.log("Login attempt `onLoginButtonClicked` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.showTips(i18n.t("login.tips.WECHAT_LOGIN_FAILED_TAP_SCREEN_TO_RETRY") + ", errorMsg =" + errMsg); - self.createAuthorizeThenLoginButton(); - }, - timeout: function() { - console.log("Login attempt `onLoginButtonClicked` failed, about to execute `clearBoundRoomIdInBothVolatileAndPersistentStorage`."); - cc.sys.localStorage.removeItem("selfPlayer"); - window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); - self.showTips(i18n.t("login.tips.WECHAT_LOGIN_FAILED_TAP_SCREEN_TO_RETRY")); - self.createAuthorizeThenLoginButton(); - }, - }); - }, - - getWechatCode(evt) { - let self = this; - self.showTips(""); - const wechatServerEndpoint = wechatAddress.PROTOCOL + "://" + wechatAddress.HOST + ((null != wechatAddress.PORT && "" != wechatAddress.PORT.trim()) ? (":" + wechatAddress.PORT) : ""); - const url = wechatServerEndpoint + constants.WECHAT.AUTHORIZE_PATH + "?" + wechatAddress.APPID_LITERAL + "&" + constants.WECHAT.REDIRECT_RUI_KEY + NetworkUtils.encode(window.location.href) + "&" + constants.WECHAT.RESPONSE_TYPE + "&" + constants.WECHAT.SCOPE + constants.WECHAT.FIN; - console.log("To visit wechat auth addr: ", url); - window.location.href = url; - }, - - initWxSdk() { - const selfPlayer = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); - const origUrl = window.location.protocol + "//" + window.location.host + window.location.pathname; - /* - * The `shareLink` must - * - have its 2nd-order-domain registered as trusted 2nd-order under the targetd `res.jsConfig.app_id`, and - * - extracted from current window.location.href. - */ - const shareLink = origUrl; - const updateAppMsgShareDataObj = { - type: 'link', // 分享类型,music、video或link,不填默认为link - dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 - title: document.title, // 分享标题 - desc: 'Let\'s play together!', // 分享描述 - link: shareLink + (cc.sys.localStorage.getItem('boundRoomId') ? "" : ("?expectedRoomId=" + cc.sys.localStorage.getItem('boundRoomId'))), - imgUrl: origUrl + "/favicon.ico", // 分享图标 - success: function() { - // 设置成功 - } - }; - const menuShareTimelineObj = { - title: document.title, // 分享标题 - link: shareLink + (cc.sys.localStorage.getItem('boundRoomId') ? "" : ("?expectedRoomId=" + cc.sys.localStorage.getItem('boundRoomId'))), - imgUrl: origUrl + "/favicon.ico", // 分享图标 - success: function() {} - }; - - const wxConfigUrl = (window.isUsingWebkitWechatKernel() ? window.atFirstLocationHref : window.location.href); - - //接入微信登录接口 - NetworkUtils.ajax({ - "url": backendAddress.PROTOCOL + '://' + backendAddress.HOST + ':' + backendAddress.PORT + constants.ROUTE_PATH.API + constants.ROUTE_PATH.PLAYER + constants.ROUTE_PATH.VERSION + constants.ROUTE_PATH.WECHAT + constants.ROUTE_PATH.JSCONFIG, - type: "POST", - data: { - "url": wxConfigUrl, - "intAuthToken": selfPlayer.intAuthToken, - }, - success: function(res) { - if (constants.RET_CODE.OK != res.ret) { - console.warn("Failed to get `wsConfig`: ", res); - return; - } - const jsConfig = res.jsConfig; - const configData = { - debug: CC_DEBUG, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 - appId: jsConfig.app_id, // 必填,公众号的唯一标识 - timestamp: jsConfig.timestamp.toString(), // 必填,生成签名的时间戳 - nonceStr: jsConfig.nonce_str, // 必填,生成签名的随机串 - jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline'], - signature: jsConfig.signature, // 必填,签名 - }; - console.log("config url: ", wxConfigUrl); - console.log("wx.config: ", configData); - wx.config(configData); - console.log("Current window.location.href: ", window.location.href); - wx.ready(function() { - console.log("Here is wx.ready.") - wx.onMenuShareAppMessage(updateAppMsgShareDataObj); - wx.onMenuShareTimeline(menuShareTimelineObj); - }); - wx.error(function(res) { - console.error("wx config fails and error is ", JSON.stringify(res)); - }); - }, - error: function(xhr, status, errMsg) { - console.error("Failed to get `wsConfig`: ", errMsg); - }, - }); - }, - - update(dt) { - const self = this; - if (null != wxDownloader && 0 < wxDownloader.totalBytesExpectedToWriteForAllTasks) { - self.writtenBytes.string = wxDownloader.totalBytesWrittenForAllTasks; - self.expectedToWriteBytes.string = wxDownloader.totalBytesExpectedToWriteForAllTasks; - self.downloadProgress.progress = 1.0*wxDownloader.totalBytesWrittenForAllTasks/wxDownloader.totalBytesExpectedToWriteForAllTasks; - } - const totalUrlsToHandle = (wxDownloader.immediateHandleItemCount + wxDownloader.immediateReadFromLocalCount + wxDownloader.immediatePackDownloaderCount); - const totalUrlsHandled = (wxDownloader.immediateHandleItemCompleteCount + wxDownloader.immediateReadFromLocalCompleteCount + wxDownloader.immediatePackDownloaderCompleteCount); - if (null != wxDownloader && 0 < totalUrlsToHandle) { - self.handledUrlsCount.string = totalUrlsHandled; - self.toHandledUrlsCount.string = totalUrlsToHandle; - self.handlerProgress.progress = 1.0*totalUrlsHandled/totalUrlsToHandle; - } - } -}); diff --git a/frontend/assets/scripts/WsSessionMgr.js b/frontend/assets/scripts/WsSessionMgr.js index baa36ee..6d81061 100644 --- a/frontend/assets/scripts/WsSessionMgr.js +++ b/frontend/assets/scripts/WsSessionMgr.js @@ -74,8 +74,6 @@ function _base64ToUint8Array(base64) { origBytes[i] = origBinaryStr.charCodeAt(i); } return origBytes; - } else if (cc.sys.platform == cc.sys.WECHAT_GAME) { - return Buffer.from(base64, 'base64'); } else { return null; } @@ -86,16 +84,12 @@ function _base64ToArrayBuffer(base64) { } window.getExpectedRoomIdSync = function() { - if (cc.sys.platform == cc.sys.WECHAT_GAME) { - return window.expectedRoomId; + const qDict = window.getQueryParamDict(); + if (qDict) { + return qDict["expectedRoomId"]; } else { - const qDict = window.getQueryParamDict(); - if (qDict) { - return qDict["expectedRoomId"]; - } else { - if (window.history && window.history.state) { - return window.history.state.expectedRoomId; - } + if (window.history && window.history.state) { + return window.history.state.expectedRoomId; } } @@ -129,10 +123,6 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) { if (null != expectedRoomId) { console.log("initPersistentSessionClient with expectedRoomId == " + expectedRoomId); urlToConnect = urlToConnect + "&expectedRoomId=" + expectedRoomId; - if (cc.sys.platform == cc.sys.WECHAT_GAME) { - // This is a dirty hack. -- YFLu - window.expectedRoomId = null; - } } else { window.boundRoomId = getBoundRoomIdFromPersistentStorage(); if (null != window.boundRoomId) { @@ -143,10 +133,6 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) { const currentHistoryState = window.history && window.history.state ? window.history.state : {}; - if (cc.sys.platform != cc.sys.WECHAT_GAME) { - window.history.replaceState(currentHistoryState, document.title, window.location.pathname); - } - const clientSession = new WebSocket(urlToConnect); clientSession.binaryType = 'arraybuffer'; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob" @@ -254,10 +240,6 @@ window.clearLocalStorageAndBackToLoginScene = function(shouldRetainBoundRoomIdIn if (true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage) { window.clearBoundRoomIdInBothVolatileAndPersistentStorage(); } - if (cc.sys.platform == cc.sys.WECHAT_GAME) { - cc.director.loadScene('wechatGameLogin'); - } else { - cc.director.loadScene('login'); - } + cc.director.loadScene('login'); }; diff --git a/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js b/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js index 6a8d59e..bd97e12 100644 --- a/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js +++ b/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js @@ -4343,7 +4343,6 @@ $root.treasurehunterx = (function() { * @property {Object.|null} [traps] RoomDownsyncFrame traps * @property {Object.|null} [bullets] RoomDownsyncFrame bullets * @property {Object.|null} [speedShoes] RoomDownsyncFrame speedShoes - * @property {Object.|null} [pumpkin] RoomDownsyncFrame pumpkin * @property {Object.|null} [guardTowers] RoomDownsyncFrame guardTowers * @property {Object.|null} [playerMetas] RoomDownsyncFrame playerMetas */ @@ -4362,7 +4361,6 @@ $root.treasurehunterx = (function() { this.traps = {}; this.bullets = {}; this.speedShoes = {}; - this.pumpkin = {}; this.guardTowers = {}; this.playerMetas = {}; if (properties) @@ -4443,14 +4441,6 @@ $root.treasurehunterx = (function() { */ RoomDownsyncFrame.prototype.speedShoes = $util.emptyObject; - /** - * RoomDownsyncFrame pumpkin. - * @member {Object.} pumpkin - * @memberof treasurehunterx.RoomDownsyncFrame - * @instance - */ - RoomDownsyncFrame.prototype.pumpkin = $util.emptyObject; - /** * RoomDownsyncFrame guardTowers. * @member {Object.} guardTowers @@ -4524,19 +4514,14 @@ $root.treasurehunterx = (function() { writer.uint32(/* id 9, wireType 2 =*/74).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); $root.treasurehunterx.SpeedShoe.encode(message.speedShoes[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); } - if (message.pumpkin != null && Object.hasOwnProperty.call(message, "pumpkin")) - for (var keys = Object.keys(message.pumpkin), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 10, wireType 2 =*/82).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); - $root.treasurehunterx.Pumpkin.encode(message.pumpkin[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); - } if (message.guardTowers != null && Object.hasOwnProperty.call(message, "guardTowers")) for (var keys = Object.keys(message.guardTowers), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 11, wireType 2 =*/90).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); + writer.uint32(/* id 10, wireType 2 =*/82).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); $root.treasurehunterx.GuardTower.encode(message.guardTowers[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); } if (message.playerMetas != null && Object.hasOwnProperty.call(message, "playerMetas")) for (var keys = Object.keys(message.playerMetas), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 12, wireType 2 =*/98).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); + writer.uint32(/* id 11, wireType 2 =*/90).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); $root.treasurehunterx.PlayerMeta.encode(message.playerMetas[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); } return writer; @@ -4705,29 +4690,6 @@ $root.treasurehunterx = (function() { break; } case 10: { - if (message.pumpkin === $util.emptyObject) - message.pumpkin = {}; - var end2 = reader.uint32() + reader.pos; - key = 0; - value = null; - while (reader.pos < end2) { - var tag2 = reader.uint32(); - switch (tag2 >>> 3) { - case 1: - key = reader.int32(); - break; - case 2: - value = $root.treasurehunterx.Pumpkin.decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag2 & 7); - break; - } - } - message.pumpkin[key] = value; - break; - } - case 11: { if (message.guardTowers === $util.emptyObject) message.guardTowers = {}; var end2 = reader.uint32() + reader.pos; @@ -4750,7 +4712,7 @@ $root.treasurehunterx = (function() { message.guardTowers[key] = value; break; } - case 12: { + case 11: { if (message.playerMetas === $util.emptyObject) message.playerMetas = {}; var end2 = reader.uint32() + reader.pos; @@ -4890,20 +4852,6 @@ $root.treasurehunterx = (function() { } } } - if (message.pumpkin != null && message.hasOwnProperty("pumpkin")) { - if (!$util.isObject(message.pumpkin)) - return "pumpkin: object expected"; - var key = Object.keys(message.pumpkin); - for (var i = 0; i < key.length; ++i) { - if (!$util.key32Re.test(key[i])) - return "pumpkin: integer key{k:int32} expected"; - { - var error = $root.treasurehunterx.Pumpkin.verify(message.pumpkin[key[i]]); - if (error) - return "pumpkin." + error; - } - } - } if (message.guardTowers != null && message.hasOwnProperty("guardTowers")) { if (!$util.isObject(message.guardTowers)) return "guardTowers: object expected"; @@ -5019,16 +4967,6 @@ $root.treasurehunterx = (function() { message.speedShoes[keys[i]] = $root.treasurehunterx.SpeedShoe.fromObject(object.speedShoes[keys[i]]); } } - if (object.pumpkin) { - if (typeof object.pumpkin !== "object") - throw TypeError(".treasurehunterx.RoomDownsyncFrame.pumpkin: object expected"); - message.pumpkin = {}; - for (var keys = Object.keys(object.pumpkin), i = 0; i < keys.length; ++i) { - if (typeof object.pumpkin[keys[i]] !== "object") - throw TypeError(".treasurehunterx.RoomDownsyncFrame.pumpkin: object expected"); - message.pumpkin[keys[i]] = $root.treasurehunterx.Pumpkin.fromObject(object.pumpkin[keys[i]]); - } - } if (object.guardTowers) { if (typeof object.guardTowers !== "object") throw TypeError(".treasurehunterx.RoomDownsyncFrame.guardTowers: object expected"); @@ -5071,7 +5009,6 @@ $root.treasurehunterx = (function() { object.traps = {}; object.bullets = {}; object.speedShoes = {}; - object.pumpkin = {}; object.guardTowers = {}; object.playerMetas = {}; } @@ -5129,11 +5066,6 @@ $root.treasurehunterx = (function() { for (var j = 0; j < keys2.length; ++j) object.speedShoes[keys2[j]] = $root.treasurehunterx.SpeedShoe.toObject(message.speedShoes[keys2[j]], options); } - if (message.pumpkin && (keys2 = Object.keys(message.pumpkin)).length) { - object.pumpkin = {}; - for (var j = 0; j < keys2.length; ++j) - object.pumpkin[keys2[j]] = $root.treasurehunterx.Pumpkin.toObject(message.pumpkin[keys2[j]], options); - } if (message.guardTowers && (keys2 = Object.keys(message.guardTowers)).length) { object.guardTowers = {}; for (var j = 0; j < keys2.length; ++j) From cccbeb1c2982a4077b3b31bdad90417e102061c8 Mon Sep 17 00:00:00 2001 From: genxium Date: Sun, 25 Sep 2022 20:48:09 +0800 Subject: [PATCH 3/7] Fixed part of frame chasing dynamics, yet collision handling is still broken. --- .../assets/resources/prefabs/Pacman1.prefab | 134 ++++++++------- .../assets/resources/prefabs/Pacman2.prefab | 134 ++++++++------- frontend/assets/scripts/BasePlayer.js | 4 +- frontend/assets/scripts/Map.js | 160 ++++++++++-------- 4 files changed, 229 insertions(+), 203 deletions(-) diff --git a/frontend/assets/resources/prefabs/Pacman1.prefab b/frontend/assets/resources/prefabs/Pacman1.prefab index bfc71fe..7d5723c 100644 --- a/frontend/assets/resources/prefabs/Pacman1.prefab +++ b/frontend/assets/resources/prefabs/Pacman1.prefab @@ -8,7 +8,8 @@ "__id__": 1 }, "optimizationPolicy": 0, - "asyncLoadAssets": false + "asyncLoadAssets": false, + "readonly": false }, { "__type__": "cc.Node", @@ -27,7 +28,6 @@ } ], "_active": true, - "_level": 1, "_components": [ { "__id__": 11 @@ -63,17 +63,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 2, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -89,7 +78,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 2, + "groupIndex": 2, + "_id": "" }, { "__type__": "cc.Node", @@ -100,7 +101,6 @@ }, "_children": [], "_active": false, - "_level": 0, "_components": [ { "__id__": 3 @@ -127,17 +127,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -153,7 +142,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.Label", @@ -163,6 +164,7 @@ "__id__": 2 }, "_enabled": true, + "_materials": [], "_useOriginalSize": false, "_string": "(0, 0)", "_N$string": "(0, 0)", @@ -200,7 +202,6 @@ }, "_children": [], "_active": false, - "_level": 2, "_components": [ { "__id__": 6 @@ -227,17 +228,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -253,7 +243,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.ParticleSystem", @@ -263,6 +265,9 @@ "__id__": 5 }, "_enabled": true, + "_materials": [], + "_srcBlendFactor": 770, + "_dstBlendFactor": 1, "_custom": true, "_file": { "__uuid__": "b2687ac4-099e-403c-a192-ff477686f4f5" @@ -271,8 +276,6 @@ "__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3" }, "_texture": null, - "_srcBlendFactor": 770, - "_dstBlendFactor": 1, "_stopped": false, "playOnLoad": true, "autoRemoveOnFinish": false, @@ -329,6 +332,7 @@ "x": 7, "y": 7 }, + "_positionType": 1, "positionType": 1, "emitterMode": 0, "gravity": { @@ -372,7 +376,6 @@ }, "_children": [], "_active": true, - "_level": 2, "_components": [ { "__id__": 9 @@ -399,17 +402,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -425,7 +417,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.Sprite", @@ -435,6 +439,13 @@ "__id__": 8 }, "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, "_spriteFrame": { "__uuid__": "a2170e4c-df31-41ef-be73-f4f605e75821" }, @@ -449,12 +460,9 @@ "_fillStart": 0, "_fillRange": 0, "_isTrimmedMode": true, - "_state": 0, "_atlas": { "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" }, - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, "_id": "" }, { @@ -476,6 +484,9 @@ "__id__": 1 }, "_enabled": true, + "_materials": [], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, "_spriteFrame": null, "_type": 0, "_sizeMode": 0, @@ -488,10 +499,7 @@ "_fillStart": 0, "_fillRange": 0, "_isTrimmedMode": true, - "_state": 0, "_atlas": null, - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, "_id": "" }, { @@ -549,8 +557,8 @@ }, "_enabled": true, "animComp": null, - "baseSpeed": 300, - "speed": 200, + "baseSpeed": 50, + "speed": 50, "lastMovedAt": 0, "eps": 0.1, "magicLeanLowerBound": 0.414, diff --git a/frontend/assets/resources/prefabs/Pacman2.prefab b/frontend/assets/resources/prefabs/Pacman2.prefab index 84058c2..cc5b6c8 100644 --- a/frontend/assets/resources/prefabs/Pacman2.prefab +++ b/frontend/assets/resources/prefabs/Pacman2.prefab @@ -8,7 +8,8 @@ "__id__": 1 }, "optimizationPolicy": 0, - "asyncLoadAssets": false + "asyncLoadAssets": false, + "readonly": false }, { "__type__": "cc.Node", @@ -27,7 +28,6 @@ } ], "_active": true, - "_level": 1, "_components": [ { "__id__": 11 @@ -63,17 +63,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 2, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -89,7 +78,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 2, + "groupIndex": 2, + "_id": "" }, { "__type__": "cc.Node", @@ -100,7 +101,6 @@ }, "_children": [], "_active": false, - "_level": 0, "_components": [ { "__id__": 3 @@ -127,17 +127,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -153,7 +142,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.Label", @@ -163,6 +164,7 @@ "__id__": 2 }, "_enabled": true, + "_materials": [], "_useOriginalSize": false, "_string": "(0, 0)", "_N$string": "(0, 0)", @@ -200,7 +202,6 @@ }, "_children": [], "_active": false, - "_level": 2, "_components": [ { "__id__": 6 @@ -227,17 +228,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -253,7 +243,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.ParticleSystem", @@ -263,6 +265,9 @@ "__id__": 5 }, "_enabled": true, + "_materials": [], + "_srcBlendFactor": 770, + "_dstBlendFactor": 1, "_custom": true, "_file": { "__uuid__": "b2687ac4-099e-403c-a192-ff477686f4f5" @@ -271,8 +276,6 @@ "__uuid__": "472df5d3-35e7-4184-9e6c-7f41bee65ee3" }, "_texture": null, - "_srcBlendFactor": 770, - "_dstBlendFactor": 1, "_stopped": false, "playOnLoad": true, "autoRemoveOnFinish": false, @@ -329,6 +332,7 @@ "x": 7, "y": 7 }, + "_positionType": 1, "positionType": 1, "emitterMode": 0, "gravity": { @@ -372,7 +376,6 @@ }, "_children": [], "_active": true, - "_level": 2, "_components": [ { "__id__": 9 @@ -399,17 +402,6 @@ "x": 0.5, "y": 0.5 }, - "_quat": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_skewX": 0, - "_skewY": 0, - "groupIndex": 0, - "_id": "", "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", @@ -425,7 +417,19 @@ 1, 1 ] - } + }, + "_eulerAngles": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_skewX": 0, + "_skewY": 0, + "_is3DNode": false, + "_groupIndex": 0, + "groupIndex": 0, + "_id": "" }, { "__type__": "cc.Sprite", @@ -435,6 +439,13 @@ "__id__": 8 }, "_enabled": true, + "_materials": [ + { + "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" + } + ], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, "_spriteFrame": { "__uuid__": "a2170e4c-df31-41ef-be73-f4f605e75821" }, @@ -449,12 +460,9 @@ "_fillStart": 0, "_fillRange": 0, "_isTrimmedMode": true, - "_state": 0, "_atlas": { "__uuid__": "030d9286-e8a2-40cf-98f8-baf713f0b8c4" }, - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, "_id": "" }, { @@ -476,6 +484,9 @@ "__id__": 1 }, "_enabled": true, + "_materials": [], + "_srcBlendFactor": 770, + "_dstBlendFactor": 771, "_spriteFrame": null, "_type": 0, "_sizeMode": 0, @@ -488,10 +499,7 @@ "_fillStart": 0, "_fillRange": 0, "_isTrimmedMode": true, - "_state": 0, "_atlas": null, - "_srcBlendFactor": 770, - "_dstBlendFactor": 771, "_id": "" }, { @@ -549,8 +557,8 @@ }, "_enabled": true, "animComp": null, - "baseSpeed": 300, - "speed": 200, + "baseSpeed": 50, + "speed": 50, "lastMovedAt": 0, "eps": 0.1, "magicLeanLowerBound": 0.414, diff --git a/frontend/assets/scripts/BasePlayer.js b/frontend/assets/scripts/BasePlayer.js index 2d1840b..72d2b53 100644 --- a/frontend/assets/scripts/BasePlayer.js +++ b/frontend/assets/scripts/BasePlayer.js @@ -8,11 +8,11 @@ module.export = cc.Class({ }, baseSpeed: { type: cc.Float, - default: 300, + default: 50, }, speed: { type: cc.Float, - default: 300 + default: 50 }, lastMovedAt: { type: cc.Float, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 482c8ef..507a562 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -614,30 +614,31 @@ cc.Class({ } if (null != firstPredictedYetIncorrectInputFrameId) { - const renderFrameId2 = self.renderFrameId; - const inputFrameId2 = self._convertToInputFrameId(renderFrameId2, self.inputDelayFrames); const inputFrameId1 = firstPredictedYetIncorrectInputFrameId; const renderFrameId1 = self._convertToRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId" - if (renderFrameId1 < renderFrameId2) { + if (renderFrameId1 < self.renderFrameId) { /* A typical case is as follows. -------------------------------------------------------- - [lastAllConfirmedRenderFrameId] : 22 + [self.lastAllConfirmedRenderFrameId] : 22 - : 36 + : 36 - [chaserRenderFrameId] : 62 + : 62 - : 64 + [self.renderFrameId] : 64 -------------------------------------------------------- */ - console.warn("Mismatched input detected!: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1); - // The actual rollback-and-replay would later be executed in update(dt). - self.chaserRenderFrameId = renderFrameId1; + if (renderFrameId1 < self.chaserRenderFrameId) { + // The actual rollback-and-replay would later be executed in update(dt). + console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId); + self.chaserRenderFrameId = renderFrameId1; + } else { + // Deliberately left blank, chasing is ongoing. + } } else { - // No need to rollback when "renderFrameId1 == renderFrameId2", because the "delayedInputFrame for renderFrameId2" is not yet executed by now, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range. - console.log("Mismatched input yet no rollback needed: [inputFrameId1:", inputFrameId1, ", inputFrameId2:", inputFrameId2, "), [renderFrameId1:", renderFrameId1, ", renderFrameId2:", renderFrameId2, "). "); + // No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId2" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range. } } }; @@ -718,6 +719,9 @@ cc.Class({ self.applyRoomDownsyncFrameDynamics(rdf); self._dumpToRenderCache(rdf); self.battleState = ALL_BATTLE_STATES.IN_BATTLE; // Starts the increment of "self.renderFrameId" in "self.update(dt)" + if (null != window.boundRoomId) { + self.boundRoomIdLabel.string = window.boundRoomId; + } }, logBattleStats() { @@ -725,9 +729,10 @@ cc.Class({ let s = []; s.push("Battle stats: lastUpsyncInputFrameId=" + self.lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self.lastAllConfirmedInputFrameId); - self.recentInputCache.forEach((inputFrameDownsync, inputFrameId) => { + for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { + const inputFrameDownsync = self.recentInputCache.getByFrameId(i); s.push(JSON.stringify(inputFrameDownsync)); - }); + } console.log(s.join('\n')); }, @@ -776,65 +781,64 @@ cc.Class({ update(dt) { const self = this; - try { - if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) { - let prevSelfInput = null, currSelfInput = null; - const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here - if (self._shouldGenerateInputFrameUpsync(self.renderFrameId)) { - const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId); - prevSelfInput = prevAndCurrInputs[0]; - currSelfInput = prevAndCurrInputs[1]; + if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) { + try { + let prevSelfInput = null, currSelfInput = null; + const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here + if (self._shouldGenerateInputFrameUpsync(self.renderFrameId)) { + const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId); + prevSelfInput = prevAndCurrInputs[0]; + currSelfInput = prevAndCurrInputs[1]; + } + + let t0 = performance.now(); + if (self.shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) { + // TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously? + self.sendInputFrameUpsyncBatch(noDelayInputFrameId); + } + + let t1 = performance.now(); + const prevChaserRenderFrameId = self.chaserRenderFrameId; + let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); + if (nextChaserRenderFrameId > self.renderFrameId) nextChaserRenderFrameId = self.renderFrameId; + self.rollbackAndReplay(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); + self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! + let t2 = performance.now(); + + // Inside "self.rollbackAndReplay", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. + const rdf = self.rollbackAndReplay(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); + + self.applyRoomDownsyncFrameDynamics(rdf); + let t3 = performance.now(); + /* + if (prevChaserRenderFrameId < nextChaserRenderFrameId) { + console.log("Took ", t1-t0, " milliseconds to send upsync cmds, ", t2-t1, " milliseconds to chase renderFrameIds=[", prevChaserRenderFrameId, ", ", nextChaserRenderFrameId, "], @renderFrameId=", self.renderFrameId); + } + */ + } catch (err) { + console.error("Error during Map.update", err); + } finally { + // Update camera to track selfPlayer. + if (null != self.ctrl) { + self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); } - let t0 = performance.now(); - if (self.shouldSendInputFrameUpsyncBatch(prevSelfInput, currSelfInput, self.lastUpsyncInputFrameId, noDelayInputFrameId)) { - // TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously? - self.sendInputFrameUpsyncBatch(noDelayInputFrameId); - } + // Update countdown + if (null != self.countdownNanos) { + self.countdownNanos -= self.rollbackEstimatedDt*1000000000; + if (self.countdownNanos <= 0) { + self.onBattleStopped(self.playerRichInfoDict); + return; + } - let t1 = performance.now(); - if (self.chaserRenderFrameId <= self.renderFrameId) { - let chaserUpperRenderFrameId = (self.chaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); - if (chaserUpperRenderFrameId > self.renderFrameId) chaserUpperRenderFrameId = self.renderFrameId; - self.rollbackAndReplay(self.chaserRenderFrameId, chaserUpperRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); - self.chaserRenderFrameId = chaserUpperRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! + const countdownSeconds = parseInt(self.countdownNanos / 1000000000); + if (isNaN(countdownSeconds)) { + console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`); + } + self.countdownLabel.string = countdownSeconds; } - let t2 = performance.now(); - - // Inside "self.rollbackAndReplay", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. - const rdf = self.rollbackAndReplay(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); - - self.applyRoomDownsyncFrameDynamics(rdf); - let t3 = performance.now(); - ++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!! } - const mapNode = self.node; - const canvasNode = mapNode.parent; - const canvasParentNode = canvasNode.parent; - if (null != window.boundRoomId) { - self.boundRoomIdLabel.string = window.boundRoomId; - } - - // update countdown - if (null != self.countdownNanos) { - self.countdownNanos -= self.rollbackEstimatedDt*1000000000; - if (self.countdownNanos <= 0) { - self.onBattleStopped(self.playerRichInfoDict); - return; - } - - const countdownSeconds = parseInt(self.countdownNanos / 1000000000); - if (isNaN(countdownSeconds)) { - console.warn(`countdownSeconds is NaN for countdownNanos == ${self.countdownNanos}.`); - } - self.countdownLabel.string = countdownSeconds; - } - } catch (err) { - console.error("Error during Map.update", err); - } - if (null != self.ctrl) { - self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); } }, @@ -996,7 +1000,7 @@ cc.Class({ getCachedInputFrameDownsyncWithPrediction(inputFrameId) { const self = this; let inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); - if (-1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) { + if (null != inputFrameDownsync && -1 != self.lastAllConfirmedInputFrameId && inputFrameId > self.lastAllConfirmedInputFrameId) { const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { if (i == self.selfPlayerInfo.joinIndex-1) continue; @@ -1008,8 +1012,7 @@ cc.Class({ }, rollbackAndReplay(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { - if (renderFrameSt == renderFrameIdEd) { - console.warn("Unexpected input!"); + if (renderFrameSt >= renderFrameIdEd) { return; } @@ -1031,18 +1034,25 @@ cc.Class({ /* This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd". */ - for (let i = renderFrameSt; i < renderFrameIdEd; ++i) { + for (let i = renderFrameIdSt; i < renderFrameIdEd; ++i) { const renderFrame = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame" const j = self._convertToInputFrameId(i, self.inputDelayFrames); - const inputs = self.recentInputCache.getByFrameId(j).inputList; + const inputList = self.getCachedInputFrameDownsyncWithPrediction(j).inputList; self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { const joinIndex = playerRichInfo.joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); const player = renderFrame.players[playerId]; - const decodedInput = self.ctrl.decodeDirection(inputs[joinIndex-1]); - playerCollider.x += player.speed*decodedInput.dx*self.rollbackEstimatedDt; - playerCollider.y += player.speed*decodedInput.dy*self.rollbackEstimatedDt; + const encodedInput = inputList[joinIndex-1]; + const decodedInput = self.ctrl.decodeDirection(encodedInput); + const baseChange = player.speed*self.rollbackEstimatedDt; + playerCollider.x += baseChange*decodedInput.dx; + playerCollider.y += baseChange*decodedInput.dy; + /* + if (0 < encodedInput) { + console.log("playerId=", playerId, "@renderFrameId=", i, ", delayedInputFrameId=", j, ", baseChange=", baseChange, ": x=", playerCollider.x, ", y=", playerCollider.y); + } + */ }); collisionSys.update(); From 114e6b05011cd02b4c76109fac402be39cfa83fd Mon Sep 17 00:00:00 2001 From: genxium Date: Sun, 25 Sep 2022 23:21:12 +0800 Subject: [PATCH 4/7] Updated documentation. --- ConcerningEdgeCases.md | 6 +++--- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scripts/Map.js | 11 ++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ConcerningEdgeCases.md b/ConcerningEdgeCases.md index 825888f..d9a84b8 100644 --- a/ConcerningEdgeCases.md +++ b/ConcerningEdgeCases.md @@ -1,11 +1,11 @@ Under the current "input delay" algorithm, the lag of a single player would cause all the other players to receive outdated commands, e.g. when at a certain moment -- player#1: renderFrameId = 100, significantly lagged due to local CPU being overheated +- player#1: renderFrameId = 100, significantly lagged due to local CPU overheated - player#2: renderFrameId = 240 - player#3: renderFrameId = 239 - player#4: renderFrameId = 242 -players #2, #3 #4 would receive "outdated(in their subjective feelings)" from then on, and be forced to rollback many frames. +players #2, #3 #4 would receive "outdated(in their subjective feelings) but all-confirmed commands" from then on, thus forced to rollback and chase many frames - the lag due to "large range of frame-chasing" would then further deteriorate the situation - like an avalanche. -In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. On the frontend we could only mitigate the impact to players #2, #3, #4 by certain buffering mechanisms. +In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `/frontend/assets/scripts/Map.js, function update(dt)` for more information. However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames. diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 020e9a8..31ccba3 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 216.05530045313827, + 209.73151519075364, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 507a562..8706c26 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -631,7 +631,7 @@ cc.Class({ -------------------------------------------------------- */ if (renderFrameId1 < self.chaserRenderFrameId) { - // The actual rollback-and-replay would later be executed in update(dt). + // The actual rollback-and-chase would later be executed in update(dt). console.warn("Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:", inputFrameId1, ", renderFrameId1:", renderFrameId1, ", chaserRenderFrameId before reset: ", self.chaserRenderFrameId); self.chaserRenderFrameId = renderFrameId1; } else { @@ -798,15 +798,16 @@ cc.Class({ } let t1 = performance.now(); + // Use "fractional-frame-chasing" to guarantee that "self.update(dt)" is not jammed by a "large range of frame-chasing". See `/ConcerningEdgeCases.md` for the motivation. const prevChaserRenderFrameId = self.chaserRenderFrameId; let nextChaserRenderFrameId = (prevChaserRenderFrameId + self.maxChasingRenderFramesPerUpdate); if (nextChaserRenderFrameId > self.renderFrameId) nextChaserRenderFrameId = self.renderFrameId; - self.rollbackAndReplay(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); + self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.chaserCollisionSys, self.chaserCollisionSysMap); self.chaserRenderFrameId = nextChaserRenderFrameId; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic! let t2 = performance.now(); - // Inside "self.rollbackAndReplay", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. - const rdf = self.rollbackAndReplay(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); + // Inside "self.rollbackAndChase", the "self.latestCollisionSys" is ALWAYS ROLLED BACK to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding inputFrameDownsync, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. + const rdf = self.rollbackAndChase(self.renderFrameId, self.renderFrameId+1, self.latestCollisionSys, self.latestCollisionSysMap); self.applyRoomDownsyncFrameDynamics(rdf); let t3 = performance.now(); @@ -1011,7 +1012,7 @@ cc.Class({ return inputFrameDownsync; }, - rollbackAndReplay(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { + rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap) { if (renderFrameSt >= renderFrameIdEd) { return; } From 50273c981bcc99fa6921755f8de7084c7bcc6898 Mon Sep 17 00:00:00 2001 From: genxium Date: Sun, 25 Sep 2022 23:30:34 +0800 Subject: [PATCH 5/7] Minor fix. --- ConcerningEdgeCases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConcerningEdgeCases.md b/ConcerningEdgeCases.md index d9a84b8..f828f41 100644 --- a/ConcerningEdgeCases.md +++ b/ConcerningEdgeCases.md @@ -6,6 +6,6 @@ Under the current "input delay" algorithm, the lag of a single player would caus players #2, #3 #4 would receive "outdated(in their subjective feelings) but all-confirmed commands" from then on, thus forced to rollback and chase many frames - the lag due to "large range of frame-chasing" would then further deteriorate the situation - like an avalanche. -In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `/frontend/assets/scripts/Map.js, function update(dt)` for more information. +In a "no-server & p2p" setup, I couldn't think of a proper way to cope with such edge case. Solely on the frontend we could only mitigate the impact to players #2, #3, #4, e.g. a potential lag due to "large range of frame-chasing" is proactively avoided in `/frontend/assets/scripts/Map.js, function update(dt)`. However in a "server as authority" setup, the server could force confirming an inputFrame without player#1's upsync, and notify player#1 to apply a "roomDownsyncFrame" as well as drop all its outdated local inputFrames. From 2dfd6083c559384593a263f0ae434b9cfac5cef6 Mon Sep 17 00:00:00 2001 From: genxium Date: Mon, 26 Sep 2022 10:36:46 +0800 Subject: [PATCH 6/7] Fixed a few indexing errors. --- battle_srv/models/room.go | 2 +- frontend/assets/scripts/FindingPlayer.js | 16 --- frontend/assets/scripts/Map.js | 150 +++++++++++------------ 3 files changed, 71 insertions(+), 97 deletions(-) diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index f4e5e7c..cb74cc6 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -977,7 +977,7 @@ func (pR *Room) StopBattleForSettlement() { for playerId, _ := range pR.Players { assembledFrame := pb.RoomDownsyncFrame{ Id: pR.Tick, - RefFrameId: 0, // Hardcoded for now. + RefFrameId: pR.Tick, // Hardcoded for now. Players: toPbPlayers(pR.Players), SentAt: utils.UnixtimeMilli(), CountdownNanos: -1, // TODO: Replace this magic constant! diff --git a/frontend/assets/scripts/FindingPlayer.js b/frontend/assets/scripts/FindingPlayer.js index 0262366..628f25a 100644 --- a/frontend/assets/scripts/FindingPlayer.js +++ b/frontend/assets/scripts/FindingPlayer.js @@ -90,23 +90,7 @@ cc.Class({ if (remoteUrl == null || remoteUrl == '') { cc.log(`No avatar to show for :`); cc.log(playerMeta); - remoteUrl = 'http://wx.qlogo.cn/mmopen/PiajxSqBRaEJUWib5D85KXWHumaxhU4E9XOn9bUpCNKF3F4ibfOj8JYHCiaoosvoXCkTmOQE1r2AKKs8ObMaz76EdA/0' } - cc.loader.load({ - url: remoteUrl, - type: 'jpg' - }, function(err, texture) { - if (null != err ) { - console.error(err); - } else { - if (null == texture) { - return; - } - const sf = new cc.SpriteFrame(); - sf.setTexture(texture); - playerInfoNode.getChildByName('avatarMask').getChildByName('avatar').getComponent(cc.Sprite).spriteFrame = sf; - } - }); })(); function isEmptyString(str) { diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 8706c26..f8e7c9f 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -137,9 +137,12 @@ cc.Class({ _dumpToRenderCache: function(roomDownsyncFrame) { const self = this; const minToKeepRenderFrameId = self.lastAllConfirmedRenderFrameId; - while (self.recentInputCache.stFrameId < minToKeepRenderFrameId) { + while (0 < self.recentRenderCache.cnt && self.recentRenderCache.stFrameId < minToKeepRenderFrameId) { self.recentRenderCache.pop(); } + if (self.recentRenderCache.stFrameId < minToKeepRenderFrameId) { + console.warn("Weird dumping of RENDER frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId); + } const existing = self.recentRenderCache.getByFrameId(roomDownsyncFrame.id); if (null != existing) { existing.players = roomDownsyncFrame.players; @@ -157,10 +160,14 @@ cc.Class({ _dumpToInputCache: function(inputFrameDownsync) { const self = this; - const minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction. - while (self.recentInputCache.stFrameId < minToKeepInputFrameId) { + let minToKeepInputFrameId = self._convertToInputFrameId(self.lastAllConfirmedRenderFrameId, self.inputDelayFrames); // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding inputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction. + if (minToKeepInputFrameId > self.lastAllConfirmedInputFrameId) minToKeepInputFrameId = self.lastAllConfirmedInputFrameId; + while (0 < self.recentInputCache.cnt && self.recentInputCache.stFrameId < minToKeepInputFrameId) { self.recentInputCache.pop(); } + if (self.recentInputCache.stFrameId < minToKeepInputFrameId) { + console.warn("Weird dumping of INPUT frame: self.renderFrame=", self.renderFrame, ", self.recentInputCache=", self._stringifyRecentInputCache(false), ", self.recentRenderCache=", self._stringifyRecentRenderCache(false), ", self.lastAllConfirmedRenderFrameId=", self.lastAllConfirmedRenderFrameId, ", self.lastAllConfirmedInputFrameId=", self.lastAllConfirmedInputFrameId); + } const existing = self.recentInputCache.getByFrameId(inputFrameDownsync.inputFrameId); if (null != existing) { existing.inputList = inputFrameDownsync.inputList; @@ -532,54 +539,38 @@ cc.Class({ self.transitToState(ALL_MAP_STATES.WAITING); self._inputControlEnabled = false; - let findingPlayerScriptIns = null; + let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); window.handleRoomDownsyncFrame = function(rdf) { if (ALL_BATTLE_STATES.WAITING != self.battleState && ALL_BATTLE_STATES.IN_BATTLE != self.battleState && ALL_BATTLE_STATES.IN_SETTLEMENT != self.battleState) { return; } + + const frameId = rdf.id; // Right upon establishment of the "PersistentSessionClient", we should receive an initial signal "BattleColliderInfo" earlier than any "RoomDownsyncFrame" containing "PlayerMeta" data. const refFrameId = rdf.refFrameId; switch (refFrameId) { case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_ADDED_AND_ACKED: - self._initPlayerRichInfoDict(rdf.players, rdf.playerMetas); - // 显示匹配玩家 - findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); + // Update the "finding player" GUI and show it if not previously present if (!self.findingPlayerNode.parent) { self.showPopupInCanvas(self.findingPlayerNode); } findingPlayerScriptIns.updatePlayersInfo(rdf.playerMetas); return; - case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_READDED_AND_ACKED: - self._initPlayerRichInfoDict(rdf.players, rdf.playerMetas); - // In this case, we're definitely in an active battle, thus the "self.findingPlayerNode" should be hidden if being presented. - if (self.findingPlayerNode && self.findingPlayerNode.parent) { - self.findingPlayerNode.parent.removeChild(self.findingPlayerNode); - self.transitToState(ALL_MAP_STATES.VISUAL); - if (self.playersInfoNode) { - for (let playerId in rdf.playerMetas) { - const playerMeta = rdf.playerMetas[playerId]; - const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo"); - playersInfoScriptIns.updateData(playerMeta); - } - } - } - return; case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_READY_TO_START: - // 显示倒计时 - self.playersMatched(rdf.playerMetas); - // 隐藏返回按钮 - findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); - findingPlayerScriptIns.hideExitButton(); + self.onBattleReadyToStart(rdf.playerMetas, false); return; case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START: self.onBattleStarted(rdf); return; + case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_READDED_AND_ACKED: + self.lastAllConfirmedRenderFrameId = frameId; + self.onBattleReadyToStart(rdf.playerMetas, true); + self.onBattleStarted(rdf); + return; } - const frameId = rdf.id; - self.lastAllConfirmedRenderFrameId = frameId; // TODO: Inject a NetworkDoctor as introduced in https://app.yinxiang.com/shard/s61/nl/13267014/5c575124-01db-419b-9c02-ec81f78c6ddc/. }; @@ -698,10 +689,19 @@ cc.Class({ onBattleStarted(rdf) { // This function is also applicable to "re-joining". - const players = rdf.players; - const playerMetas = rdf.playerMetas; console.log('On battle started!'); const self = window.mapIns; + const players = rdf.players; + const playerMetas = rdf.playerMetas; + self._initPlayerRichInfoDict(players, playerMetas); + + // Show the top status indicators for IN_BATTLE + const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo"); + for (let i in playerMetas) { + const playerMeta = playerMetas[i]; + playersInfoScriptIns.updateData(playerMeta); + } + if (null != rdf.countdownNanos) { self.countdownNanos = rdf.countdownNanos; } @@ -715,7 +715,7 @@ cc.Class({ self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode); } self.transitToState(ALL_MAP_STATES.VISUAL); - self.chaserRenderFrameId = MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START; + self.chaserRenderFrameId = rdf.id; self.applyRoomDownsyncFrameDynamics(rdf); self._dumpToRenderCache(rdf); self.battleState = ALL_BATTLE_STATES.IN_BATTLE; // Starts the increment of "self.renderFrameId" in "self.update(dt)" @@ -912,27 +912,29 @@ cc.Class({ setLocalZOrder(toShowNode, 10); }, - playersMatched(playerMetas) { - console.log("Calling `playersMatched` with:", playerMetas); - + onBattleReadyToStart(playerMetas, isSelfRejoining) { + console.log("Calling `onBattleReadyToStart` with:", playerMetas); const self = this; const findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); + findingPlayerScriptIns.hideExitButton(); findingPlayerScriptIns.updatePlayersInfo(playerMetas); - window.setTimeout(() => { - if (null != self.findingPlayerNode.parent) { - self.findingPlayerNode.parent.removeChild(self.findingPlayerNode); - self.transitToState(ALL_MAP_STATES.VISUAL); - const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo"); - for (let i in playerMetas) { - const playerMeta = playerMetas[i]; - playersInfoScriptIns.updateData(playerMeta); - } - } - const countDownScriptIns = self.countdownToBeginGameNode.getComponent("CountdownToBeginGame"); - countDownScriptIns.setData(); - self.showPopupInCanvas(self.countdownToBeginGameNode); - return; - }, 2000); + + const hideFindingPlayersGUI = function() { + if (null == self.findingPlayerNode.parent) return; + self.findingPlayerNode.parent.removeChild(self.findingPlayerNode); + }; + + if (true == isSelfRejoining) { + hideFindingPlayersGUI(); + } else { + // Delay to hide the "finding player" GUI, then show a countdown clock + window.setTimeout(() => { + hideFindingPlayersGUI(); + const countDownScriptIns = self.countdownToBeginGameNode.getComponent("CountdownToBeginGame"); + countDownScriptIns.setData(); + self.showPopupInCanvas(self.countdownToBeginGameNode); + }, 1500); + } }, _createRoomDownsyncFrameLocally(renderFrameId, collisionSys, collisionSysMap) { @@ -1019,6 +1021,9 @@ cc.Class({ const self = this; const renderFrameSt = self.recentRenderCache.getByFrameId(renderFrameIdSt); // typed "RoomDownsyncFrame" + if (null == renderFrameSt) { + console.error("Couldn't find renderFrameId=", renderFrameIdSt, " to rollback, recentRenderCache=", self._stringifyRecentRenderCache(false)); + } /* Reset "position" of players in "collisionSys" according to "renderFrameSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics. */ @@ -1031,7 +1036,6 @@ cc.Class({ playerCollider.y = player.y; }); - const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle? /* This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd". */ @@ -1057,6 +1061,7 @@ cc.Class({ }); collisionSys.update(); + const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle? self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { const joinIndex = playerRichInfo.joinIndex; @@ -1099,44 +1104,29 @@ cc.Class({ }, _stringifyRecentInputCache(usefullOutput) { + const self = this; if (true == usefullOutput) { let s = []; - self.recentInputCache.forEach((inputFrameDownsync, inputFrameId) => { - s.push(JSON.stringify(inputFrameDownsync)); - }); + for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { + s.push(JSON.stringify(self.recentInputCache.getByFrameId(i))); + } return s.join('\n'); } - return "[stInputFrameId=" + self.recentInputCacheSt + ", edInputFrameId=" + self.recentInputCacheEd + ")"; + return "[stInputFrameId=" + self.recentInputCache.stFrameId + ", edInputFrameId=" + self.recentInputCache.edFrameId + ")"; }, - _stringifyRollbackResult(renderFrameId, delayedInputFrameDownsync) { + _stringifyRecentRenderCache(usefullOutput) { const self = this; - const s = ( - null == delayedInputFrameDownsync - ? - { - renderFrameId: renderFrameId, - players: {} + if (true == usefullOutput) { + let s = []; + for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) { + s.push(JSON.stringify(self.recentRenderCache.getByFrameId(i))); } - : - { - renderFrameId: renderFrameId, - players: {}, - delayedInputFrameDownsync: delayedInputFrameDownsync, - } - ); - self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { - const joinIndex = playerRichInfo.joinIndex; - const playerNode = playerRichInfo.node; - const playerScriptIns = playerRichInfo.scriptIns; - s.players[playerRichInfo.id] = { - id: playerRichInfo.id, - x: playerNode.position.x, - y: playerNode.position.y, - }; - }); - return JSON.stringify(s); + return s.join('\n'); + } + return "[stRenderFrameId=" + self.recentRenderCache.stFrameId + ", edRenderFrameId=" + self.recentRenderCache.edFrameId + ")"; }, + }); From 80dc05a92baeba9dd0651eccf0b1b12203ba6e55 Mon Sep 17 00:00:00 2001 From: genxium Date: Mon, 26 Sep 2022 11:16:18 +0800 Subject: [PATCH 7/7] Fixed collision detection and camera tracking. --- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scripts/CameraTracker.js | 2 +- frontend/assets/scripts/Map.js | 28 +-- frontend/assets/scripts/TouchEventsManager.js | 71 ++----- frontend/assets/scripts/modules/Collisions.js | 198 +++++++++++++++++- 5 files changed, 234 insertions(+), 67 deletions(-) diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 31ccba3..020e9a8 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 209.73151519075364, + 216.05530045313827, 0, 0, 0, diff --git a/frontend/assets/scripts/CameraTracker.js b/frontend/assets/scripts/CameraTracker.js index 37ab4c9..d822237 100644 --- a/frontend/assets/scripts/CameraTracker.js +++ b/frontend/assets/scripts/CameraTracker.js @@ -22,7 +22,7 @@ cc.Class({ if (!self.mapScriptIns) return; if (!self.mapScriptIns.selfPlayerInfo) return; if (!self.mapScriptIns.playerRichInfoDict) return; - const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict[self.mapScriptIns.selfPlayerInfo.id]; + const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id); if (!selfPlayerRichInfo) return; const selfPlayerNode = selfPlayerRichInfo.node; if (!selfPlayerNode) return; diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index f8e7c9f..b92a56f 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -126,7 +126,7 @@ cc.Class({ }, maxChasingRenderFramesPerUpdate: { type: cc.Integer, - default: 5 + default: 10 }, }, @@ -565,7 +565,9 @@ cc.Class({ self.onBattleStarted(rdf); return; case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_READDED_AND_ACKED: - self.lastAllConfirmedRenderFrameId = frameId; + // [WARNING] The "frameId" from server could be quite fast-forwarding, don't assign it in other cases. + self.renderFrameId = frameId; + self.lastAllConfirmedRenderFrameId = frameId; self.onBattleReadyToStart(rdf.playerMetas, true); self.onBattleStarted(rdf); return; @@ -763,8 +765,8 @@ cc.Class({ newPlayerNode.getComponent("SelfPlayer").mapNode = self.node; const currentSelfColliderCircle = newPlayerNode.getComponent(cc.CircleCollider); - const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle); - const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle); + const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); + const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; self.latestCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderLatest); self.chaserCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderChaser); @@ -819,11 +821,6 @@ cc.Class({ } catch (err) { console.error("Error during Map.update", err); } finally { - // Update camera to track selfPlayer. - if (null != self.ctrl) { - self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); - } - // Update countdown if (null != self.countdownNanos) { self.countdownNanos -= self.rollbackEstimatedDt*1000000000; @@ -1050,7 +1047,7 @@ cc.Class({ const player = renderFrame.players[playerId]; const encodedInput = inputList[joinIndex-1]; const decodedInput = self.ctrl.decodeDirection(encodedInput); - const baseChange = player.speed*self.rollbackEstimatedDt; + const baseChange = player.speed*self.rollbackEstimatedDt*decodedInput.speedFactor; playerCollider.x += baseChange*decodedInput.dx; playerCollider.y += baseChange*decodedInput.dy; /* @@ -1063,8 +1060,9 @@ cc.Class({ collisionSys.update(); const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle? - self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { - const joinIndex = playerRichInfo.joinIndex; + // [WARNING] Traverse in the order of joinIndices to guarantee determinism. + for (let i in self.playerRichInfoArr) { + const joinIndex = parseInt(i) + 1; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); const potentials = playerCollider.potentials(); @@ -1075,7 +1073,7 @@ cc.Class({ playerCollider.x -= result.overlap * result.overlap_x; playerCollider.y -= result.overlap * result.overlap_y; } - }); + } } return self._createRoomDownsyncFrameLocally(renderFrameIdEd, collisionSys, collisionSysMap); @@ -1101,6 +1099,10 @@ cc.Class({ nodeAndScriptIns[1].showArrowTipNode(); } } + self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + self.playerRichInfoArr[playerRichInfo.joinIndex-1] = playerRichInfo; + }); }, _stringifyRecentInputCache(usefullOutput) { diff --git a/frontend/assets/scripts/TouchEventsManager.js b/frontend/assets/scripts/TouchEventsManager.js index 22c1554..4a1e459 100644 --- a/frontend/assets/scripts/TouchEventsManager.js +++ b/frontend/assets/scripts/TouchEventsManager.js @@ -1,19 +1,24 @@ window.DIRECTION_DECODER = [ - [0, 0], - [0, +1], - [0, -1], - [+2, 0], - [-2, 0], - [+2, +1], - [-2, -1], - [+2, -1], - [-2, +1], - [+2, 0], - [-2, 0], - [0, +1], - [0, -1], + [0, 0, null], + [0, +1, null], + [0, -1, null], + [+2, 0, null], + [-2, 0, null], + [+2, +1, null], + [-2, -1, null], + [+2, -1, null], + [-2, +1, null], + [+2, 0, null], + [-2, 0, null], + [0, +1, null], + [0, -1, null], ]; +for (let k in window.DIRECTION_DECODER) { + const length = Math.sqrt(window.DIRECTION_DECODER[k][0]*window.DIRECTION_DECODER[k][0] + window.DIRECTION_DECODER[k][1]*window.DIRECTION_DECODER[k][1]); + window.DIRECTION_DECODER[k][2] = (0 == length ? 0 : (1.0/length)); +} + cc.Class({ extends: cc.Component, properties: { @@ -114,43 +119,6 @@ cc.Class({ this.initialized = true; }, - justifyMapNodePosAndScale(linearSpeedBase, zoomingSpeedBase) { - const self = this; - if (false == self.mapScriptIns._inputControlEnabled) return; - if (null != self._cachedMapNodePosTarget) { - while (self.maxMovingBufferLength < self._cachedMapNodePosTarget.length) { - self._cachedMapNodePosTarget.shift(); - } - if (0 < self._cachedMapNodePosTarget.length && 0 == self.mapNode.getNumberOfRunningActions()) { - const nextMapNodePosTarget = self._cachedMapNodePosTarget.shift(); - const linearSpeed = linearSpeedBase; - const finalDiffVec = nextMapNodePosTarget.pos.sub(self.mapNode.position); - const finalDiffVecMag = finalDiffVec.mag(); - if (self.linearMovingEps > finalDiffVecMag) { - // Jittering. - // cc.log("Map node moving by finalDiffVecMag == %s is just jittering.", finalDiffVecMag); - return; - } - const durationSeconds = finalDiffVecMag / linearSpeed; - cc.log("Map node moving to %o in %s/%s == %s seconds.", nextMapNodePosTarget.pos, finalDiffVecMag, linearSpeed, durationSeconds); - const bufferedTargetPos = cc.v2(nextMapNodePosTarget.pos.x, nextMapNodePosTarget.pos.y); - self.mapNode.runAction(cc.sequence( - cc.moveTo(durationSeconds, bufferedTargetPos), - cc.callFunc(() => { - if (self._isMapOverMoved(self.mapNode.position)) { - self.mapNode.setPosition(bufferedTargetPos); - } - }, self) - )); - } - } - if (null != self._cachedZoomRawTarget && false == self._cachedZoomRawTarget.processed) { - cc.log(`Processing self._cachedZoomRawTarget == ${self._cachedZoomRawTarget}`); - self._cachedZoomRawTarget.processed = true; - self.mapNode.setScale(self._cachedZoomRawTarget.scale); - } - }, - _initTouchEvent() { const self = this; const translationListenerNode = (self.translationListenerNode ? self.translationListenerNode : self.mapNode); @@ -377,7 +345,8 @@ cc.Class({ } return { dx: mapped[0], - dy: mapped[1] + dy: mapped[1], + speedFactor: mapped[2], } }, diff --git a/frontend/assets/scripts/modules/Collisions.js b/frontend/assets/scripts/modules/Collisions.js index a53753d..6805e83 100644 --- a/frontend/assets/scripts/modules/Collisions.js +++ b/frontend/assets/scripts/modules/Collisions.js @@ -1 +1,197 @@ -!function(_){var t={};function s(n){if(t[n])return t[n].exports;var e=t[n]={i:n,l:!1,exports:{}};return _[n].call(e.exports,e,e.exports,s),e.l=!0,e.exports}s.m=_,s.c=t,s.d=function(_,t,n){s.o(_,t)||Object.defineProperty(_,t,{enumerable:!0,get:n})},s.r=function(_){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(_,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(_,"__esModule",{value:!0})},s.t=function(_,t){if(1&t&&(_=s(_)),8&t)return _;if(4&t&&"object"==typeof _&&_&&_.__esModule)return _;var n=Object.create(null);if(s.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:_}),2&t&&"string"!=typeof _)for(var e in _)s.d(n,e,function(t){return _[t]}.bind(null,e));return n},s.n=function(_){var t=_&&_.__esModule?function(){return _.default}:function(){return _};return s.d(t,"a",t),t},s.o=function(_,t){return Object.prototype.hasOwnProperty.call(_,t)},s.p="",s(s.s=0)}([function(_,t,s){"use strict";s.r(t);const n=[];class e{constructor(){this._bvh_parent=null,this._bvh_branch=!0,this._bvh_left=null,this._bvh_right=null,this._bvh_sort=0,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}static getBranch(){return n.length?n.pop():new e}static releaseBranch(_){n.push(_)}static sortBranches(_,t){return _.sort>t.sort?-1:1}}class i{constructor(){this._hierarchy=null,this._bodies=[],this._dirty_branches=[]}insert(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=this,this._bodies.push(_)}const s=_._polygon,n=_.x,i=_.y;s&&(_._dirty_coords||_.x!==_._x||_.y!==_._y||_.angle!==_._angle||_.scale_x!==_._scale_x||_.scale_y!==_._scale_y)&&_._calculateCoords();const r=_._bvh_padding,h=s?0:_.radius*_.scale,o=(s?_._min_x:n-h)-r,l=(s?_._min_y:i-h)-r,a=(s?_._max_x:n+h)+r,c=(s?_._max_y:i+h)+r;_._bvh_min_x=o,_._bvh_min_y=l,_._bvh_max_x=a,_._bvh_max_y=c;let b=this._hierarchy,v=0;if(b)for(;;){if(!b._bvh_branch){const t=b._bvh_parent,s=b._bvh_min_x,n=b._bvh_min_y,i=b._bvh_max_x,r=b._bvh_max_y,h=b._bvh_parent=_._bvh_parent=e.getBranch();h._bvh_parent=t,h._bvh_left=b,h._bvh_right=_,h._bvh_sort=v++,h._bvh_min_x=oi?a:i,h._bvh_max_y=c>r?c:r,t?t._bvh_left===b?t._bvh_left=h:t._bvh_right=h:this._hierarchy=h;break}{const _=b._bvh_left,t=_._bvh_min_y,s=_._bvh_max_x,n=_._bvh_max_y,e=o<_._bvh_min_x?o:_._bvh_min_x,i=ls?a:s,h=c>n?c:n,u=(r-e)*(h-i)-(s-_._bvh_min_x)*(n-t),y=b._bvh_right,x=y._bvh_min_x,m=y._bvh_min_y,d=y._bvh_max_x,f=y._bvh_max_y,p=od?a:d,P=c>f?c:f,M=(w-p)*(P-g)-(d-x)*(f-m);b._bvh_sort=v++,b._bvh_min_x=ew?r:w,b._bvh_max_y=h>P?h:P,b=u<=M?_:y}}else this._hierarchy=_}remove(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=null,this._bodies.splice(this._bodies.indexOf(_),1)}if(this._hierarchy===_)return void(this._hierarchy=null);const s=_._bvh_parent,n=s._bvh_parent,i=s._bvh_left,r=i===_?s._bvh_right:i;if(r._bvh_parent=n,r._bvh_branch&&(r._bvh_sort=s._bvh_sort),n){n._bvh_left===s?n._bvh_left=r:n._bvh_right=r;let _=n;for(;_;){const t=_._bvh_left,s=t._bvh_min_x,n=t._bvh_min_y,e=t._bvh_max_x,i=t._bvh_max_y,r=_._bvh_right,h=r._bvh_min_x,o=r._bvh_min_y,l=r._bvh_max_x,a=r._bvh_max_y;_._bvh_min_x=sl?e:l,_._bvh_max_y=i>a?i:a,_=_._bvh_parent}}else this._hierarchy=r;e.releaseBranch(s)}update(){const _=this._bodies,t=_.length;for(let s=0;st._bvh_max_x||l>t._bvh_max_y}n&&(this.remove(t,!0),this.insert(t,!0))}}potentials(_){const t=[],s=_._bvh_min_x,n=_._bvh_min_y,e=_._bvh_max_x,i=_._bvh_max_y;let r=this._hierarchy,h=!0;if(!r||!r._bvh_branch)return t;for(;r;){if(h){h=!1;let _=r._bvh_branch?r._bvh_left:null;for(;_&&_._bvh_max_x>=s&&_._bvh_max_y>=n&&_._bvh_min_x<=e&&_._bvh_min_y<=i;)r=_,_=r._bvh_branch?r._bvh_left:null}const o=r._bvh_branch,l=o?r._bvh_right:null;if(l&&l._bvh_max_x>s&&l._bvh_max_y>n&&l._bvh_min_xu&&l>y}(_,t)||(e&&_._dirty_normals&&_._calculateNormals(),i&&t._dirty_normals&&t._calculateNormals(),r=e&&i?function(_,t,s=null){const n=_._coords.length,e=t._coords.length;if(2===n&&2===e){const n=_._coords,e=t._coords;return s&&(s.overlap=0),n[0]===e[0]&&n[1]===e[1]}const i=_._coords,r=t._coords,h=_._normals,o=t._normals;if(n>2)for(let _=0,t=1;_2)for(let _=0,t=1;_h*h)return!1;if(s){const _=Math.sqrt(o);s.a_in_b=n<=e&&_<=e-n,s.b_in_a=e<=n&&_<=n-e,s.overlap=h-_,s.overlap_x=i/_,s.overlap_y=r/_}return!0}(_,t,s)),s&&(s.collision=r),r}function o(_,t,s=null,n=!1){const e=_._coords,i=_._edges,r=_._normals,h=t.x,o=t.y,l=t.radius*t.scale,a=2*l,c=l*l,b=e.length;let v=!0,u=!0,y=null,x=0,m=0;if(2===b){const _=h-e[0],t=o-e[1],n=_*_+t*t;if(n>c)return!1;if(s){const s=Math.sqrt(n);y=l-s,x=_/s,m=t/s,u=!1}}else for(let _=0,t=1;_f*f+p*p?1:0;let P=!1,M=0,T=0,B=0;if(s&&v&&n*n+d*d>c&&(v=!1),w){const t=-1===w,r=t?0===_?b-2:_-2:_===b-2?0:_+2,a=r+1,v=h-e[r],y=o-e[a],x=i[r],m=i[a],f=v*x+y*m;if((f<0?-1:f>x*x+m*m?1:0)===-w){const _=t?n:v,e=t?d:y,i=_*_+e*e;if(i>c)return!1;if(s){const t=Math.sqrt(i);P=!0,M=l-t,T=_/t,B=e/t,u=!1}}}else{const e=r[_],i=r[t],h=n*e+d*i,o=h<0?-h:h;if(h>0&&o>l)return!1;s&&(P=!0,M=l-h,T=e,B=i,(u&&h>=0||MM)&&(y=M,x=T,m=B)}return s&&(s.a_in_b=n?u:v,s.b_in_a=n?v:u,s.overlap=y,s.overlap_x=n?-x:x,s.overlap_y=n?-m:m),!0}function l(_,t,s,n,e=null){const i=_.length,r=t.length;if(!i||!r)return!0;let h=null,o=null,l=null,a=null;for(let t=0,e=1;ti)&&(h=i),(null===o||oi)&&(l=i),(null===a||aa||oa)_=h-a,e.a_in_b=!1;else{const t=o-l,s=a-h;_=ti){const t=_<0?-1:1;e.overlap=i,e.overlap_x=s*t,e.overlap_y=n*t}}return!1}class a{constructor(_=0,t=0,s=0){this.x=_,this.y=t,this.padding=s,this._circle=!1,this._polygon=!1,this._point=!1,this._bvh=null,this._bvh_parent=null,this._bvh_branch=!1,this._bvh_padding=s,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}collides(_,t=null,s=!0){return h(this,_,t,s)}potentials(){const _=this._bvh;if(null===_)throw new Error("Body does not belong to a collision system");return _.potentials(this)}remove(){const _=this._bvh;_&&_.remove(this,!1)}createResult(){return new r}static createResult(){return new r}}class c extends a{constructor(_=0,t=0,s=0,n=1,e=0){super(_,t,e),this.radius=s,this.scale=n}draw(_){const t=this.x,s=this.y,n=this.radius*this.scale;_.moveTo(t+n,s),_.arc(t,s,n,0,2*Math.PI)}}class b extends a{constructor(_=0,t=0,s=[],n=0,e=1,i=1,r=0){super(_,t,r),this.angle=n,this.scale_x=e,this.scale_y=i,this._polygon=!0,this._x=_,this._y=t,this._angle=n,this._scale_x=e,this._scale_y=i,this._min_x=0,this._min_y=0,this._max_x=0,this._max_y=0,this._points=null,this._coords=null,this._edges=null,this._normals=null,this._dirty_coords=!0,this._dirty_normals=!0,b.prototype.setPoints.call(this,s)}draw(_){(this._dirty_coords||this.x!==this._x||this.y!==this._y||this.angle!==this._angle||this.scale_x!==this._scale_x||this.scale_y!==this._scale_y)&&this._calculateCoords();const t=this._coords;if(2===t.length)_.moveTo(t[0],t[1]),_.arc(t[0],t[1],1,0,2*Math.PI);else{_.moveTo(t[0],t[1]);for(let s=2;s4&&_.lineTo(t[0],t[1])}}setPoints(_){const t=_.length;this._points=new Float64Array(2*t),this._coords=new Float64Array(2*t),this._edges=new Float64Array(2*t),this._normals=new Float64Array(2*t);const s=this._points;for(let n=0,e=0,i=1;nl&&(l=h),uc&&(c=u))}this._x=_,this._y=t,this._angle=s,this._scale_x=n,this._scale_y=e,this._min_x=o,this._min_y=a,this._max_x=l,this._max_y=c,this._dirty_coords=!1,this._dirty_normals=!0}_calculateNormals(){const _=this._coords,t=this._edges,s=this._normals,n=_.length;for(let e=0,i=1;e} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Polygon}\r\n\t */\r\n\tcreatePolygon(x = 0, y = 0, points = [[0, 0]], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tconst body = new _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"](x, y, points, angle, scale_x, scale_y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Point} and inserts it into the collision system\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Point}\r\n\t */\r\n\tcreatePoint(x = 0, y = 0, padding = 0) {\r\n\t\tconst body = new _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x, y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts bodies into the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tinsert(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.insert(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Removes bodies from the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tremove(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.remove(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the collision system. This should be called before any collisions are tested.\r\n\t */\r\n\tupdate() {\r\n\t\tthis._bvh.update();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the system to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\treturn this._bvh.draw(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the system's BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\treturn this._bvh.drawBVH(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test for potential collisions against\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\treturn this._bvh.potentials(body);\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if two bodies are colliding\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(source, target, result = null, aabb = true) {\r\n\t\treturn Object(_modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__[\"default\"])(source, target, result, aabb);\r\n\t}\r\n};\r\n\r\nconst toExport = {\r\n\tCollisions,\r\n\tResult: _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\r\n\tCircle: _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\r\n\tPolygon: _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\r\n\tPoint: _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\r\n};\r\n\r\nmodule.exports = toExport; \r\n\n\n//# sourceURL=webpack:///./src/Collisions.mjs?"); + +/***/ }), + +/***/ "./src/modules/BVH.mjs": +/*!*****************************!*\ + !*** ./src/modules/BVH.mjs ***! + \*****************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVH; });\n/* harmony import */ var _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./BVHBranch.mjs */ \"./src/modules/BVHBranch.mjs\");\n\r\n\r\n/**\r\n * A Bounding Volume Hierarchy (BVH) used to find potential collisions quickly\r\n * @class\r\n * @private\r\n */\r\nclass BVH {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._hierarchy = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bodies = [];\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_branches = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts a body into the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to insert\r\n\t * @param {Boolean} [updating = false] Set to true if the body already exists in the BVH (used internally when updating the body's position)\r\n\t */\r\n\tinsert(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = this;\r\n\t\t\tthis._bodies.push(body);\r\n\t\t}\r\n\r\n\t\tconst polygon = body._polygon;\r\n\t\tconst body_x = body.x;\r\n\t\tconst body_y = body.y;\r\n\r\n\t\tif(polygon) {\r\n\t\t\tif(\r\n\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t) {\r\n\t\t\t\tbody._calculateCoords();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst padding = body._bvh_padding;\r\n\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\tconst body_min_x = (polygon ? body._min_x : body_x - radius) - padding;\r\n\t\tconst body_min_y = (polygon ? body._min_y : body_y - radius) - padding;\r\n\t\tconst body_max_x = (polygon ? body._max_x : body_x + radius) + padding;\r\n\t\tconst body_max_y = (polygon ? body._max_y : body_y + radius) + padding;\r\n\r\n\t\tbody._bvh_min_x = body_min_x;\r\n\t\tbody._bvh_min_y = body_min_y;\r\n\t\tbody._bvh_max_x = body_max_x;\r\n\t\tbody._bvh_max_y = body_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet sort = 0;\r\n\r\n\t\tif(!current) {\r\n\t\t\tthis._hierarchy = body;\r\n\t\t}\r\n\t\telse {\r\n\t\t\twhile(true) {\r\n\t\t\t\t// Branch\r\n\t\t\t\tif(current._bvh_branch) {\r\n\t\t\t\t\tconst left = current._bvh_left;\r\n\t\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\t\t\t\t\tconst left_new_min_x = body_min_x < left._bvh_min_x ? body_min_x : left._bvh_min_x;\r\n\t\t\t\t\tconst left_new_min_y = body_min_y < left_min_y ? body_min_y : left_min_y;\r\n\t\t\t\t\tconst left_new_max_x = body_max_x > left_max_x ? body_max_x : left_max_x;\r\n\t\t\t\t\tconst left_new_max_y = body_max_y > left_max_y ? body_max_y : left_max_y;\r\n\t\t\t\t\tconst left_volume = (left_max_x - left._bvh_min_x) * (left_max_y - left_min_y);\r\n\t\t\t\t\tconst left_new_volume = (left_new_max_x - left_new_min_x) * (left_new_max_y - left_new_min_y);\r\n\t\t\t\t\tconst left_difference = left_new_volume - left_volume;\r\n\r\n\t\t\t\t\tconst right = current._bvh_right;\r\n\t\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\t\t\t\t\tconst right_new_min_x = body_min_x < right_min_x ? body_min_x : right_min_x;\r\n\t\t\t\t\tconst right_new_min_y = body_min_y < right_min_y ? body_min_y : right_min_y;\r\n\t\t\t\t\tconst right_new_max_x = body_max_x > right_max_x ? body_max_x : right_max_x;\r\n\t\t\t\t\tconst right_new_max_y = body_max_y > right_max_y ? body_max_y : right_max_y;\r\n\t\t\t\t\tconst right_volume = (right_max_x - right_min_x) * (right_max_y - right_min_y);\r\n\t\t\t\t\tconst right_new_volume = (right_new_max_x - right_new_min_x) * (right_new_max_y - right_new_min_y);\r\n\t\t\t\t\tconst right_difference = right_new_volume - right_volume;\r\n\r\n\t\t\t\t\tcurrent._bvh_sort = sort++;\r\n\t\t\t\t\tcurrent._bvh_min_x = left_new_min_x < right_new_min_x ? left_new_min_x : right_new_min_x;\r\n\t\t\t\t\tcurrent._bvh_min_y = left_new_min_y < right_new_min_y ? left_new_min_y : right_new_min_y;\r\n\t\t\t\t\tcurrent._bvh_max_x = left_new_max_x > right_new_max_x ? left_new_max_x : right_new_max_x;\r\n\t\t\t\t\tcurrent._bvh_max_y = left_new_max_y > right_new_max_y ? left_new_max_y : right_new_max_y;\r\n\r\n\t\t\t\t\tcurrent = left_difference <= right_difference ? left : right;\r\n\t\t\t\t}\r\n\t\t\t\t// Leaf\r\n\t\t\t\telse {\r\n\t\t\t\t\tconst grandparent = current._bvh_parent;\r\n\t\t\t\t\tconst parent_min_x = current._bvh_min_x;\r\n\t\t\t\t\tconst parent_min_y = current._bvh_min_y;\r\n\t\t\t\t\tconst parent_max_x = current._bvh_max_x;\r\n\t\t\t\t\tconst parent_max_y = current._bvh_max_y;\r\n\t\t\t\t\tconst new_parent = current._bvh_parent = body._bvh_parent = _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].getBranch();\r\n\r\n\t\t\t\t\tnew_parent._bvh_parent = grandparent;\r\n\t\t\t\t\tnew_parent._bvh_left = current;\r\n\t\t\t\t\tnew_parent._bvh_right = body;\r\n\t\t\t\t\tnew_parent._bvh_sort = sort++;\r\n\t\t\t\t\tnew_parent._bvh_min_x = body_min_x < parent_min_x ? body_min_x : parent_min_x;\r\n\t\t\t\t\tnew_parent._bvh_min_y = body_min_y < parent_min_y ? body_min_y : parent_min_y;\r\n\t\t\t\t\tnew_parent._bvh_max_x = body_max_x > parent_max_x ? body_max_x : parent_max_x;\r\n\t\t\t\t\tnew_parent._bvh_max_y = body_max_y > parent_max_y ? body_max_y : parent_max_y;\r\n\r\n\t\t\t\t\tif(!grandparent) {\r\n\t\t\t\t\t\tthis._hierarchy = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(grandparent._bvh_left === current) {\r\n\t\t\t\t\t\tgrandparent._bvh_left = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tgrandparent._bvh_right = new_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Removes a body from the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to remove\r\n\t * @param {Boolean} [updating = false] Set to true if this is a temporary removal (used internally when updating the body's position)\r\n\t */\r\n\tremove(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = null;\r\n\t\t\tthis._bodies.splice(this._bodies.indexOf(body), 1);\r\n\t\t}\r\n\r\n\t\tif(this._hierarchy === body) {\r\n\t\t\tthis._hierarchy = null;\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst parent = body._bvh_parent;\r\n\t\tconst grandparent = parent._bvh_parent;\r\n\t\tconst parent_left = parent._bvh_left;\r\n\t\tconst sibling = parent_left === body ? parent._bvh_right : parent_left;\r\n\r\n\t\tsibling._bvh_parent = grandparent;\r\n\r\n\t\tif(sibling._bvh_branch) {\r\n\t\t\tsibling._bvh_sort = parent._bvh_sort;\r\n\t\t}\r\n\r\n\t\tif(grandparent) {\r\n\t\t\tif(grandparent._bvh_left === parent) {\r\n\t\t\t\tgrandparent._bvh_left = sibling;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tgrandparent._bvh_right = sibling;\r\n\t\t\t}\r\n\r\n\t\t\tlet branch = grandparent;\r\n\r\n\t\t\twhile(branch) {\r\n\t\t\t\tconst left = branch._bvh_left;\r\n\t\t\t\tconst left_min_x = left._bvh_min_x;\r\n\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\r\n\t\t\t\tconst right = branch._bvh_right;\r\n\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\r\n\t\t\t\tbranch._bvh_min_x = left_min_x < right_min_x ? left_min_x : right_min_x;\r\n\t\t\t\tbranch._bvh_min_y = left_min_y < right_min_y ? left_min_y : right_min_y;\r\n\t\t\t\tbranch._bvh_max_x = left_max_x > right_max_x ? left_max_x : right_max_x;\r\n\t\t\t\tbranch._bvh_max_y = left_max_y > right_max_y ? left_max_y : right_max_y;\r\n\r\n\t\t\t\tbranch = branch._bvh_parent;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tthis._hierarchy = sibling;\r\n\t\t}\r\n\r\n\t\t_BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].releaseBranch(parent);\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the BVH. Moved bodies are removed/inserted.\r\n\t */\r\n\tupdate() {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tconst body = bodies[i];\r\n\r\n\t\t\tlet update = false;\r\n\r\n\t\t\tif(!update && body.padding !== body._bvh_padding) {\r\n\t\t\t\tbody._bvh_padding = body.padding;\r\n\t\t\t\tupdate = true;\r\n\t\t\t}\r\n\r\n\t\t\tif(!update) {\r\n\t\t\t\tconst polygon = body._polygon;\r\n\r\n\t\t\t\tif(polygon) {\r\n\t\t\t\t\tif(\r\n\t\t\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tbody._calculateCoords();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst x = body.x;\r\n\t\t\t\tconst y = body.y;\r\n\t\t\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\t\t\tconst min_x = polygon ? body._min_x : x - radius;\r\n\t\t\t\tconst min_y = polygon ? body._min_y : y - radius;\r\n\t\t\t\tconst max_x = polygon ? body._max_x : x + radius;\r\n\t\t\t\tconst max_y = polygon ? body._max_y : y + radius;\r\n\r\n\t\t\t\tupdate = min_x < body._bvh_min_x || min_y < body._bvh_min_y || max_x > body._bvh_max_x || max_y > body._bvh_max_y;\r\n\t\t\t}\r\n\r\n\t\t\tif(update) {\r\n\t\t\t\tthis.remove(body, true);\r\n\t\t\t\tthis.insert(body, true);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\tconst results = [];\r\n\t\tconst min_x = body._bvh_min_x;\r\n\t\tconst min_y = body._bvh_min_y;\r\n\t\tconst max_x = body._bvh_max_x;\r\n\t\tconst max_y = body._bvh_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\tif(!current || !current._bvh_branch) {\r\n\t\t\treturn results;\r\n\t\t}\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(\r\n\t\t\t\t\tleft &&\r\n\t\t\t\t\tleft._bvh_max_x >= min_x &&\r\n\t\t\t\t\tleft._bvh_max_y >= min_y &&\r\n\t\t\t\t\tleft._bvh_min_x <= max_x &&\r\n\t\t\t\t\tleft._bvh_min_y <= max_y\r\n\t\t\t\t) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tif(\r\n\t\t\t\tright &&\r\n\t\t\t\tright._bvh_max_x > min_x &&\r\n\t\t\t\tright._bvh_max_y > min_y &&\r\n\t\t\t\tright._bvh_min_x < max_x &&\r\n\t\t\t\tright._bvh_min_y < max_y\r\n\t\t\t) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(!branch && current !== body) {\r\n\t\t\t\t\tresults.push(current);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn results;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the BVH to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tbodies[i].draw(context);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(left) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst min_x = current._bvh_min_x;\r\n\t\t\tconst min_y = current._bvh_min_y;\r\n\t\t\tconst max_x = current._bvh_max_x;\r\n\t\t\tconst max_y = current._bvh_max_y;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tcontext.moveTo(min_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, min_y);\r\n\r\n\t\t\tif(right) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVH.mjs?"); + +/***/ }), + +/***/ "./src/modules/BVHBranch.mjs": +/*!***********************************!*\ + !*** ./src/modules/BVHBranch.mjs ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVHBranch; });\n/**\r\n * @private\r\n */\r\nconst branch_pool = [];\r\n\r\n/**\r\n * A branch within a BVH\r\n * @class\r\n * @private\r\n */\r\nclass BVHBranch {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_left = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_right = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_sort = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a branch from the branch pool or creates a new branch\r\n\t * @returns {BVHBranch}\r\n\t */\r\n\tstatic getBranch() {\r\n\t\tif(branch_pool.length) {\r\n\t\t\treturn branch_pool.pop();\r\n\t\t}\r\n\r\n\t\treturn new BVHBranch();\r\n\t}\r\n\r\n\t/**\r\n\t * Releases a branch back into the branch pool\r\n\t * @param {BVHBranch} branch The branch to release\r\n\t */\r\n\tstatic releaseBranch(branch) {\r\n\t\tbranch_pool.push(branch);\r\n\t}\r\n\r\n\t/**\r\n\t * Sorting callback used to sort branches by deepest first\r\n\t * @param {BVHBranch} a The first branch\r\n\t * @param {BVHBranch} b The second branch\r\n\t * @returns {Number}\r\n\t */\r\n\tstatic sortBranches(a, b) {\r\n\t\treturn a.sort > b.sort ? -1 : 1;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVHBranch.mjs?"); + +/***/ }), + +/***/ "./src/modules/Body.mjs": +/*!******************************!*\ + !*** ./src/modules/Body.mjs ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Body; });\n/* harmony import */ var _Result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _SAT_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SAT.mjs */ \"./src/modules/SAT.mjs\");\n\r\n\r\n\r\n/**\r\n * The base class for bodies used to detect collisions\r\n * @class\r\n * @protected\r\n */\r\nclass Body {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\t/**\r\n\t\t * @desc The X coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.x = x;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.y = y;\r\n\r\n\t\t/**\r\n\t\t * @desc The amount to pad the bounding volume when testing for potential collisions\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._circle = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if the body is colliding with another body\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(target, result = null, aabb = true) {\r\n\t\treturn Object(_SAT_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(this, target, result, aabb);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh === null) {\r\n\t\t\tthrow new Error('Body does not belong to a collision system');\r\n\t\t}\r\n\r\n\t\treturn bvh.potentials(this);\r\n\t}\r\n\r\n\t/**\r\n\t * Removes the body from its current collision system\r\n\t */\r\n\tremove() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh) {\r\n\t\t\tbvh.remove(this, false);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Body.mjs?"); + +/***/ }), + +/***/ "./src/modules/Circle.mjs": +/*!********************************!*\ + !*** ./src/modules/Circle.mjs ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Circle; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A circle used to detect collisions\r\n * @class\r\n */\r\nclass Circle extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [radius = 0] The radius\r\n\t * @param {Number} [scale = 1] The scale\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.radius = radius;\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale = scale;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the circle to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the arc to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst radius = this.radius * this.scale;\r\n\r\n\t\tcontext.moveTo(x + radius, y);\r\n\t\tcontext.arc(x, y, radius, 0, Math.PI * 2);\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Circle.mjs?"); + +/***/ }), + +/***/ "./src/modules/Point.mjs": +/*!*******************************!*\ + !*** ./src/modules/Point.mjs ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Point; });\n/* harmony import */ var _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n\r\n\r\n/**\r\n * A point used to detect collisions\r\n * @class\r\n */\r\nclass Point extends _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\tsuper(x, y, [[0, 0]], 0, 1, 1, padding);\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = true;\r\n\t}\r\n};\r\n\r\nPoint.prototype.setPoints = undefined;\r\n\n\n//# sourceURL=webpack:///./src/modules/Point.mjs?"); + +/***/ }), + +/***/ "./src/modules/Polygon.mjs": +/*!*********************************!*\ + !*** ./src/modules/Polygon.mjs ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Polygon; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A polygon used to detect collisions\r\n * @class\r\n */\r\nclass Polygon extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, points = [], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc The angle of the body in radians\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.angle = angle;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the X axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_x = scale_x;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the Y axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_y = scale_y;\r\n\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._x = x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._y = y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._angle = angle;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_x = scale_x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_y = scale_y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._points = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._coords = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._edges = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._normals = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_coords = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_normals = true;\r\n\r\n\t\tPolygon.prototype.setPoints.call(this, points);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the polygon to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the shape to\r\n\t */\r\n\tdraw(context) {\r\n\t\tif(\r\n\t\t\tthis._dirty_coords ||\r\n\t\t\tthis.x !== this._x ||\r\n\t\t\tthis.y !== this._y ||\r\n\t\t\tthis.angle !== this._angle ||\r\n\t\t\tthis.scale_x !== this._scale_x ||\r\n\t\t\tthis.scale_y !== this._scale_y\r\n\t\t) {\r\n\t\t\tthis._calculateCoords();\r\n\t\t}\r\n\r\n\t\tconst coords = this._coords;\r\n\r\n\t\tif(coords.length === 2) {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\t\t\tcontext.arc(coords[0], coords[1], 1, 0, Math.PI * 2);\r\n\t\t}\r\n\t\telse {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\r\n\t\t\tfor(let i = 2; i < coords.length; i += 2) {\r\n\t\t\t\tcontext.lineTo(coords[i], coords[i + 1]);\r\n\t\t\t}\r\n\r\n\t\t\tif(coords.length > 4) {\r\n\t\t\t\tcontext.lineTo(coords[0], coords[1]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the points making up the polygon. It's important to use this function when changing the polygon's shape to ensure internal data is also updated.\r\n\t * @param {Array} new_points An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t */\r\n\tsetPoints(new_points) {\r\n\t\tconst count = new_points.length;\r\n\r\n\t\tthis._points = new Float64Array(count * 2);\r\n\t\tthis._coords = new Float64Array(count * 2);\r\n\t\tthis._edges = new Float64Array(count * 2);\r\n\t\tthis._normals = new Float64Array(count * 2);\r\n\r\n\t\tconst points = this._points;\r\n\r\n\t\tfor(let i = 0, ix = 0, iy = 1; i < count; ++i, ix += 2, iy += 2) {\r\n\t\t\tconst new_point = new_points[i];\r\n\r\n\t\t\tpoints[ix] = new_point[0];\r\n\t\t\tpoints[iy] = new_point[1];\r\n\t\t}\r\n\r\n\t\tthis._dirty_coords = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates and caches the polygon's world coordinates based on its points, angle, and scale\r\n\t */\r\n\t_calculateCoords() {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst angle = this.angle;\r\n\t\tconst scale_x = this.scale_x;\r\n\t\tconst scale_y = this.scale_y;\r\n\t\tconst points = this._points;\r\n\t\tconst coords = this._coords;\r\n\t\tconst count = points.length;\r\n\r\n\t\tlet min_x;\r\n\t\tlet max_x;\r\n\t\tlet min_y;\r\n\t\tlet max_y;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tlet coord_x = points[ix] * scale_x;\r\n\t\t\tlet coord_y = points[iy] * scale_y;\r\n\r\n\t\t\tif(angle) {\r\n\t\t\t\tconst cos = Math.cos(angle);\r\n\t\t\t\tconst sin = Math.sin(angle);\r\n\t\t\t\tconst tmp_x = coord_x;\r\n\t\t\t\tconst tmp_y = coord_y;\r\n\r\n\t\t\t\tcoord_x = tmp_x * cos - tmp_y * sin;\r\n\t\t\t\tcoord_y = tmp_x * sin + tmp_y * cos;\r\n\t\t\t}\r\n\r\n\t\t\tcoord_x += x;\r\n\t\t\tcoord_y += y;\r\n\r\n\t\t\tcoords[ix] = coord_x;\r\n\t\t\tcoords[iy] = coord_y;\r\n\r\n\t\t\tif(ix === 0) {\r\n\t\t\t\tmin_x = max_x = coord_x;\r\n\t\t\t\tmin_y = max_y = coord_y;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(coord_x < min_x) {\r\n\t\t\t\t\tmin_x = coord_x;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_x > max_x) {\r\n\t\t\t\t\tmax_x = coord_x;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(coord_y < min_y) {\r\n\t\t\t\t\tmin_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_y > max_y) {\r\n\t\t\t\t\tmax_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._x = x;\r\n\t\tthis._y = y;\r\n\t\tthis._angle = angle;\r\n\t\tthis._scale_x = scale_x;\r\n\t\tthis._scale_y = scale_y;\r\n\t\tthis._min_x = min_x;\r\n\t\tthis._min_y = min_y;\r\n\t\tthis._max_x = max_x;\r\n\t\tthis._max_y = max_y;\r\n\t\tthis._dirty_coords = false;\r\n\t\tthis._dirty_normals = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates the normals and edges of the polygon's sides\r\n\t */\r\n\t_calculateNormals() {\r\n\t\tconst coords = this._coords;\r\n\t\tconst edges = this._edges;\r\n\t\tconst normals = this._normals;\r\n\t\tconst count = coords.length;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst next = ix + 2 < count ? ix + 2 : 0;\r\n\t\t\tconst x = coords[next] - coords[ix];\r\n\t\t\tconst y = coords[next + 1] - coords[iy];\r\n\t\t\tconst length = x || y ? Math.sqrt(x * x + y * y) : 0;\r\n\r\n\t\t\tedges[ix] = x;\r\n\t\t\tedges[iy] = y;\r\n\t\t\tnormals[ix] = length ? y / length : 0;\r\n\t\t\tnormals[iy] = length ? -x / length : 0;\r\n\t\t}\r\n\r\n\t\tthis._dirty_normals = false;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Polygon.mjs?"); + +/***/ }), + +/***/ "./src/modules/Result.mjs": +/*!********************************!*\ + !*** ./src/modules/Result.mjs ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Result; });\n/**\r\n * An object used to collect the detailed results of a collision test\r\n *\r\n * > **Note:** It is highly recommended you recycle the same Result object if possible in order to avoid wasting memory\r\n * @class\r\n */\r\nclass Result {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/**\r\n\t\t * @desc True if a collision was detected\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.collision = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The source body tested\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.a = null;\r\n\r\n\t\t/**\r\n\t\t * @desc The target body tested against\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.b = null;\r\n\r\n\t\t/**\r\n\t\t * @desc True if A is completely contained within B\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.a_in_b = false;\r\n\r\n\t\t/**\r\n\t\t * @desc True if B is completely contained within A\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.b_in_a = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The magnitude of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The X direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_x = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_y = 0;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Result.mjs?"); + +/***/ }), + +/***/ "./src/modules/SAT.mjs": +/*!*****************************!*\ + !*** ./src/modules/SAT.mjs ***! + \*****************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return SAT; });\n/**\r\n * Determines if two bodies are colliding using the Separating Axis Theorem\r\n * @private\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own collision heuristic)\r\n * @returns {Boolean}\r\n */\r\nfunction SAT(a, b, result = null, aabb = true) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst b_polygon = b._polygon;\r\n\r\n\tlet collision = false;\r\n\r\n\tif(result) {\r\n\t\tresult.a = a;\r\n\t\tresult.b = b;\r\n\t\tresult.a_in_b = true;\r\n\t\tresult.b_in_a = true;\r\n\t\tresult.overlap = null;\r\n\t\tresult.overlap_x = 0;\r\n\t\tresult.overlap_y = 0;\r\n\t}\r\n\r\n\tif(a_polygon) {\r\n\t\tif(\r\n\t\t\ta._dirty_coords ||\r\n\t\t\ta.x !== a._x ||\r\n\t\t\ta.y !== a._y ||\r\n\t\t\ta.angle !== a._angle ||\r\n\t\t\ta.scale_x !== a._scale_x ||\r\n\t\t\ta.scale_y !== a._scale_y\r\n\t\t) {\r\n\t\t\ta._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_polygon) {\r\n\t\tif(\r\n\t\t\tb._dirty_coords ||\r\n\t\t\tb.x !== b._x ||\r\n\t\t\tb.y !== b._y ||\r\n\t\t\tb.angle !== b._angle ||\r\n\t\t\tb.scale_x !== b._scale_x ||\r\n\t\t\tb.scale_y !== b._scale_y\r\n\t\t) {\r\n\t\t\tb._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(!aabb || aabbAABB(a, b)) {\r\n\t\tif(a_polygon && a._dirty_normals) {\r\n\t\t\ta._calculateNormals();\r\n\t\t}\r\n\r\n\t\tif(b_polygon && b._dirty_normals) {\r\n\t\t\tb._calculateNormals();\r\n\t\t}\r\n\r\n\t\tcollision = (\r\n\t\t\ta_polygon && b_polygon ? polygonPolygon(a, b, result) :\r\n\t\t\ta_polygon ? polygonCircle(a, b, result, false) :\r\n\t\t\tb_polygon ? polygonCircle(b, a, result, true) :\r\n\t\t\tcircleCircle(a, b, result)\r\n\t\t);\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.collision = collision;\r\n\t}\r\n\r\n\treturn collision;\r\n};\r\n\r\n/**\r\n * Determines if two bodies' axis aligned bounding boxes are colliding\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n */\r\nfunction aabbAABB(a, b) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst a_x = a_polygon ? 0 : a.x;\r\n\tconst a_y = a_polygon ? 0 : a.y;\r\n\tconst a_radius = a_polygon ? 0 : a.radius * a.scale;\r\n\tconst a_min_x = a_polygon ? a._min_x : a_x - a_radius;\r\n\tconst a_min_y = a_polygon ? a._min_y : a_y - a_radius;\r\n\tconst a_max_x = a_polygon ? a._max_x : a_x + a_radius;\r\n\tconst a_max_y = a_polygon ? a._max_y : a_y + a_radius;\r\n\r\n\tconst b_polygon = b._polygon;\r\n\tconst b_x = b_polygon ? 0 : b.x;\r\n\tconst b_y = b_polygon ? 0 : b.y;\r\n\tconst b_radius = b_polygon ? 0 : b.radius * b.scale;\r\n\tconst b_min_x = b_polygon ? b._min_x : b_x - b_radius;\r\n\tconst b_min_y = b_polygon ? b._min_y : b_y - b_radius;\r\n\tconst b_max_x = b_polygon ? b._max_x : b_x + b_radius;\r\n\tconst b_max_y = b_polygon ? b._max_y : b_y + b_radius;\r\n\r\n\treturn a_min_x < b_max_x && a_min_y < b_max_y && a_max_x > b_min_x && a_max_y > b_min_y;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Polygon} b The target polygon to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction polygonPolygon(a, b, result = null) {\r\n\tconst a_count = a._coords.length;\r\n\tconst b_count = b._coords.length;\r\n\r\n\t// Handle points specially\r\n\tif(a_count === 2 && b_count === 2) {\r\n\t\tconst a_coords = a._coords;\r\n\t\tconst b_coords = b._coords;\r\n\r\n\t\tif(result) {\r\n\t\t\tresult.overlap = 0;\r\n\t\t}\r\n\r\n\t\treturn a_coords[0] === b_coords[0] && a_coords[1] === b_coords[1];\r\n\t}\r\n\r\n\tconst a_coords = a._coords;\r\n\tconst b_coords = b._coords;\r\n\tconst a_normals = a._normals;\r\n\tconst b_normals = b._normals;\r\n\r\n\tif(a_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, a_normals[ix], a_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, b_normals[ix], b_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if a polygon and a circle are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [reverse = false] Set to true to reverse a and b in the result parameter when testing circle->polygon instead of polygon->circle\r\n * @returns {Boolean}\r\n */\r\nfunction polygonCircle(a, b, result = null, reverse = false) {\r\n\tconst a_coords = a._coords;\r\n\tconst a_edges = a._edges;\r\n\tconst a_normals = a._normals;\r\n\tconst b_x = b.x;\r\n\tconst b_y = b.y;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst b_radius2 = b_radius * 2;\r\n\tconst radius_squared = b_radius * b_radius;\r\n\tconst count = a_coords.length;\r\n\r\n\tlet a_in_b = true;\r\n\tlet b_in_a = true;\r\n\tlet overlap = null;\r\n\tlet overlap_x = 0;\r\n\tlet overlap_y = 0;\r\n\r\n\t// Handle points specially\r\n\tif(count === 2) {\r\n\t\tconst coord_x = b_x - a_coords[0];\r\n\t\tconst coord_y = b_y - a_coords[1];\r\n\t\tconst length_squared = coord_x * coord_x + coord_y * coord_y;\r\n\r\n\t\tif(length_squared > radius_squared) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif(result) {\r\n\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\toverlap = b_radius - length;\r\n\t\t\toverlap_x = coord_x / length;\r\n\t\t\toverlap_y = coord_y / length;\r\n\t\t\tb_in_a = false;\r\n\t\t}\r\n\t}\r\n\telse {\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst coord_x = b_x - a_coords[ix];\r\n\t\t\tconst coord_y = b_y - a_coords[iy];\r\n\t\t\tconst edge_x = a_edges[ix];\r\n\t\t\tconst edge_y = a_edges[iy];\r\n\t\t\tconst dot = coord_x * edge_x + coord_y * edge_y;\r\n\t\t\tconst region = dot < 0 ? -1 : dot > edge_x * edge_x + edge_y * edge_y ? 1 : 0;\r\n\r\n\t\t\tlet tmp_overlapping = false;\r\n\t\t\tlet tmp_overlap = 0;\r\n\t\t\tlet tmp_overlap_x = 0;\r\n\t\t\tlet tmp_overlap_y = 0;\r\n\r\n\t\t\tif(result && a_in_b && coord_x * coord_x + coord_y * coord_y > radius_squared) {\r\n\t\t\t\ta_in_b = false;\r\n\t\t\t}\r\n\r\n\t\t\tif(region) {\r\n\t\t\t\tconst left = region === -1;\r\n\t\t\t\tconst other_x = left ? (ix === 0 ? count - 2 : ix - 2) : (ix === count - 2 ? 0 : ix + 2);\r\n\t\t\t\tconst other_y = other_x + 1;\r\n\t\t\t\tconst coord2_x = b_x - a_coords[other_x];\r\n\t\t\t\tconst coord2_y = b_y - a_coords[other_y];\r\n\t\t\t\tconst edge2_x = a_edges[other_x];\r\n\t\t\t\tconst edge2_y = a_edges[other_y];\r\n\t\t\t\tconst dot2 = coord2_x * edge2_x + coord2_y * edge2_y;\r\n\t\t\t\tconst region2 = dot2 < 0 ? -1 : dot2 > edge2_x * edge2_x + edge2_y * edge2_y ? 1 : 0;\r\n\r\n\t\t\t\tif(region2 === -region) {\r\n\t\t\t\t\tconst target_x = left ? coord_x : coord2_x;\r\n\t\t\t\t\tconst target_y = left ? coord_y : coord2_y;\r\n\t\t\t\t\tconst length_squared = target_x * target_x + target_y * target_y;\r\n\r\n\t\t\t\t\tif(length_squared > radius_squared) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif(result) {\r\n\t\t\t\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\t\ttmp_overlap_x = target_x / length;\r\n\t\t\t\t\t\ttmp_overlap_y = target_y / length;\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst normal_x = a_normals[ix];\r\n\t\t\t\tconst normal_y = a_normals[iy];\r\n\t\t\t\tconst length = coord_x * normal_x + coord_y * normal_y;\r\n\t\t\t\tconst absolute_length = length < 0 ? -length : length;\r\n\r\n\t\t\t\tif(length > 0 && absolute_length > b_radius) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(result) {\r\n\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\ttmp_overlap_x = normal_x;\r\n\t\t\t\t\ttmp_overlap_y = normal_y;\r\n\r\n\t\t\t\t\tif(b_in_a && length >= 0 || tmp_overlap < b_radius2) {\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif(tmp_overlapping && (overlap === null || overlap > tmp_overlap)) {\r\n\t\t\t\toverlap = tmp_overlap;\r\n\t\t\t\toverlap_x = tmp_overlap_x;\r\n\t\t\t\toverlap_y = tmp_overlap_y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.a_in_b = reverse ? b_in_a : a_in_b;\r\n\t\tresult.b_in_a = reverse ? a_in_b : b_in_a;\r\n\t\tresult.overlap = overlap;\r\n\t\tresult.overlap_x = reverse ? -overlap_x : overlap_x;\r\n\t\tresult.overlap_y = reverse ? -overlap_y : overlap_y;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two circles are colliding\r\n * @param {Circle} a The source circle to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction circleCircle(a, b, result = null) {\r\n\tconst a_radius = a.radius * a.scale;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst difference_x = b.x - a.x;\r\n\tconst difference_y = b.y - a.y;\r\n\tconst radius_sum = a_radius + b_radius;\r\n\tconst length_squared = difference_x * difference_x + difference_y * difference_y;\r\n\r\n\tif(length_squared > radius_sum * radius_sum) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\tresult.a_in_b = a_radius <= b_radius && length <= b_radius - a_radius;\r\n\t\tresult.b_in_a = b_radius <= a_radius && length <= a_radius - b_radius;\r\n\t\tresult.overlap = radius_sum - length;\r\n\t\tresult.overlap_x = difference_x / length;\r\n\t\tresult.overlap_y = difference_y / length;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are separated by an axis\r\n * @param {Array} a_coords The coordinates of the polygon to test\r\n * @param {Array} b_coords The coordinates of the polygon to test against\r\n * @param {Number} x The X direction of the axis\r\n * @param {Number} y The Y direction of the axis\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction separatingAxis(a_coords, b_coords, x, y, result = null) {\r\n\tconst a_count = a_coords.length;\r\n\tconst b_count = b_coords.length;\r\n\r\n\tif(!a_count || !b_count) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tlet a_start = null;\r\n\tlet a_end = null;\r\n\tlet b_start = null;\r\n\tlet b_end = null;\r\n\r\n\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\tconst dot = a_coords[ix] * x + a_coords[iy] * y;\r\n\r\n\t\tif(a_start === null || a_start > dot) {\r\n\t\t\ta_start = dot;\r\n\t\t}\r\n\r\n\t\tif(a_end === null || a_end < dot) {\r\n\t\t\ta_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\tconst dot = b_coords[ix] * x + b_coords[iy] * y;\r\n\r\n\t\tif(b_start === null || b_start > dot) {\r\n\t\t\tb_start = dot;\r\n\t\t}\r\n\r\n\t\tif(b_end === null || b_end < dot) {\r\n\t\t\tb_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tif(a_start > b_end || a_end < b_start) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tlet overlap = 0;\r\n\r\n\t\tif(a_start < b_start) {\r\n\t\t\tresult.a_in_b = false;\r\n\r\n\t\t\tif(a_end < b_end) {\r\n\t\t\t\toverlap = a_end - b_start;\r\n\t\t\t\tresult.b_in_a = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tresult.b_in_a = false;\r\n\r\n\t\t\tif(a_end > b_end) {\r\n\t\t\t\toverlap = a_start - b_end;\r\n\t\t\t\tresult.a_in_b = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst current_overlap = result.overlap;\r\n\t\tconst absolute_overlap = overlap < 0 ? -overlap : overlap;\r\n\r\n\t\tif(current_overlap === null || current_overlap > absolute_overlap) {\r\n\t\t\tconst sign = overlap < 0 ? -1 : 1;\r\n\r\n\t\t\tresult.overlap = absolute_overlap;\r\n\t\t\tresult.overlap_x = x * sign;\r\n\t\t\tresult.overlap_y = y * sign;\r\n\t\t}\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\n\n//# sourceURL=webpack:///./src/modules/SAT.mjs?"); + +/***/ }) + +/******/ }); \ No newline at end of file