From 402dd958501cf2a23777d4189d11658d79f90779 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 15 Jan 2023 22:18:53 +0100 Subject: [PATCH 01/82] - Reldens - v4.0.0 - Package update. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59ec3d2de..6f125c2cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reldens", - "version": "4.0.0-beta.25", + "version": "4.0.0-beta.26", "description": "Reldens - MMORPG Platform", "author": "Damian A. Pastorini", "license": "MIT", From bfb5db8ce87a39e53c449763b1cc40f0908b2c6c Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 17 Jan 2023 15:39:59 +0100 Subject: [PATCH 02/82] - Reldens - v4.0.0 - New events. --- lib/objects/server/manager.js | 20 ++++++++++++------- .../server/object/type/enemy-object.js | 3 +-- lib/respawn/server/room-respawn.js | 9 +++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/objects/server/manager.js b/lib/objects/server/manager.js index ebca6b129..56cee3088 100644 --- a/lib/objects/server/manager.js +++ b/lib/objects/server/manager.js @@ -78,6 +78,11 @@ class ObjectsManager this.attachToMessagesListeners(objectInstance, objectData); this.prepareAssetsPreload(objectData); await this.runAdditionalSetup(objectInstance, objectData); + this.events.emit('reldens.afterRunAdditionalSetup', { + objectInstance, + objectData, + objectsManager: this + }); // save object: this.roomObjects[objectInstance.objectIndex] = objectInstance; if(!this.roomObjectsByLayer[objectData.layer_name]){ @@ -93,13 +98,14 @@ class ObjectsManager prepareInitialStats(objProps) { let stats = sc.get(objProps, 'objects_stats', []); - if(0 < stats.length){ - if(!objProps.initialStats){ - objProps.initialStats = {}; - } - for(let stat of stats){ - objProps.initialStats[stat.parent_stat.key] = stat.value; - } + if(0 === stats.length){ + return; + } + if(!objProps.initialStats){ + objProps.initialStats = {}; + } + for(let stat of stats){ + objProps.initialStats[stat.parent_stat.key] = stat.value; } } diff --git a/lib/objects/server/object/type/enemy-object.js b/lib/objects/server/object/type/enemy-object.js index 393d61a36..c3e6b8fe9 100644 --- a/lib/objects/server/object/type/enemy-object.js +++ b/lib/objects/server/object/type/enemy-object.js @@ -24,7 +24,6 @@ class EnemyObject extends NpcObject { super(props); this.hasState = true; - // @TODO - BETA - Remove from config and make enemy stats load dynamically (passed on props from storage). let configStats = sc.get(props, 'initialStats', this.config.get('server/enemies/initialStats')); this.initialStats = Object.assign({}, configStats); this.stats = Object.assign({}, configStats); @@ -96,7 +95,7 @@ class EnemyObject extends NpcObject } this.events.on('reldens.sceneRoomOnCreate', (room) => { room.roomWorld.on('postBroadphase', (event) => { - if(!this.battle.inBattleWithPlayer.length){ + if(0 === this.battle.inBattleWithPlayer.length){ this.waitForPlayersToEnterRespawnArea(event, room); } }); diff --git a/lib/respawn/server/room-respawn.js b/lib/respawn/server/room-respawn.js index 4eebbfa75..dfa7ef422 100644 --- a/lib/respawn/server/room-respawn.js +++ b/lib/respawn/server/room-respawn.js @@ -77,6 +77,15 @@ class RoomRespawn if(sc.isFunction(objInstance, 'runAdditionalRespawnSetup')){ await objInstance.runAdditionalRespawnSetup(); } + this.events.emit('reldens.afterRunAdditionalRespawnSetup', { + objInstance, + clonedObjProps, + respawnArea, + multipleObj, + objClass, + objectIndex, + roomRespawn: this + }); let assetsArr = this.getObjectAssets(multipleObj); // @TODO - BETA - Objects could have multiple assets, need to implement and test the case. objInstance.clientParams.asset_key = assetsArr[0]; From 79f6d1583819f9a3e0fbedc9edb49a86fd9ef85b Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 17 Jan 2023 16:31:06 +0100 Subject: [PATCH 03/82] - Reldens - v4.0.0 - New battle end event. --- lib/actions/server/pve.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/actions/server/pve.js b/lib/actions/server/pve.js index 7645d535e..736b088f3 100644 --- a/lib/actions/server/pve.js +++ b/lib/actions/server/pve.js @@ -30,7 +30,7 @@ class Pve extends Battle { // @TODO - BETA - Make PvP available by configuration. // @NOTE: run battle method is for when the player attacks any target. PVE can be started in different ways, - // depending how the current enemy-object was implemented, for example the PVE can start when the player just + // depending on how the current enemy-object was implemented, for example the PVE can start when the player just // collides with the enemy (instead of attack it) an aggressive enemy could start the battle automatically. let attackResult = await super.runBattle(playerSchema, target, roomScene); await this.events.emit('reldens.runBattlePveAfter', {playerSchema, target, roomScene, attackResult}); @@ -39,10 +39,11 @@ class Pve extends Battle // attack for which matter we won't start the battle until the physical body hits the target. return false; } - 0 < target.stats[roomScene.config.get('client/actions/skills/affectedProperty')] - ? await this.startBattleWith(playerSchema, roomScene) - // physical attacks or effects will run the battleEnded, normal attacks or effects will hit this case: - : await this.battleEnded(playerSchema, roomScene); + if(0 < target.stats[roomScene.config.get('client/actions/skills/affectedProperty')]){ + return await this.startBattleWith(playerSchema, roomScene); + } + // physical attacks or effects will run the battleEnded, normal attacks or effects will hit this case: + return await this.battleEnded(playerSchema, roomScene); } async startBattleWith(playerSchema, room) @@ -165,6 +166,7 @@ class Pve extends Battle await this.targetObject.respawn(room); this.sendBattleEndedActionData(room, playerSchema, actionData); await this.events.emit(this.targetObject.getBattleEndEvent(), playerSchema, this, actionData); + await this.events.emit('reldens.battleEnded', {playerSchema, pve: this, actionData}); } sendBattleEndedActionData(room, playerSchema, actionData) From 7e9fd005d0406ca20a2068e4e1c546f7b0c5e991 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sat, 21 Jan 2023 21:29:38 +0100 Subject: [PATCH 04/82] - Reldens - v4.0.0 - Teams and clans plugin. --- lib/actions/client/preloader-handler.js | 4 +- lib/actions/server/player-enricher.js | 2 +- lib/config/constants.js | 9 +- lib/config/server/manager.js | 7 +- lib/features/client/config-client.js | 4 +- lib/features/server/config-server.js | 4 +- lib/game/client/room-events.js | 2 +- lib/game/client/ui-factory.js | 54 + lib/game/constants.js | 2 + lib/inventory/client/inventory-ui.js | 57 +- lib/inventory/client/plugin.js | 51 +- lib/inventory/client/templates-handler.js | 35 + lib/inventory/server/message-actions.js | 4 +- lib/objects/server/manager.js | 2 +- .../server/object/type/enemy-object.js | 2 +- .../server/object/type/trader-object.js | 2 +- lib/respawn/server/room-respawn.js | 2 +- lib/teams/client/plugin.js | 89 + lib/teams/client/team-create-target-action.js | 42 + lib/teams/constants.js | 11 + lib/teams/server/plugin.js | 30 + migrations/production/beta.26-sql-update.sql | 75 + package-lock.json | 1913 +++++++++-------- package.json | 10 +- .../features/teams/templates/team-accept.html | 4 + .../features/teams/templates/team-invite.html | 3 + .../features/teams/templates/ui-clan.html | 5 + .../features/teams/templates/ui-team.html | 5 + 28 files changed, 1391 insertions(+), 1039 deletions(-) create mode 100644 lib/game/client/ui-factory.js create mode 100644 lib/inventory/client/templates-handler.js create mode 100644 lib/teams/client/plugin.js create mode 100644 lib/teams/client/team-create-target-action.js create mode 100644 lib/teams/constants.js create mode 100644 lib/teams/server/plugin.js create mode 100644 migrations/production/beta.26-sql-update.sql create mode 100644 theme/default/assets/features/teams/templates/team-accept.html create mode 100644 theme/default/assets/features/teams/templates/team-invite.html create mode 100644 theme/default/assets/features/teams/templates/ui-clan.html create mode 100644 theme/default/assets/features/teams/templates/ui-team.html diff --git a/lib/actions/client/preloader-handler.js b/lib/actions/client/preloader-handler.js index 6352be3c1..6cd36ddee 100644 --- a/lib/actions/client/preloader-handler.js +++ b/lib/actions/client/preloader-handler.js @@ -100,7 +100,7 @@ class PreloaderHandler if(sc.hasOwn(data.animationData, ['type', 'img']) && 'spritesheet' === data.animationData.type){ this.preloadAnimationsInDirections(data, uiScene); } - if(data.classKey && sc.isFunction(data.classKey, 'prepareAnimation')){ + if(data.classKey && sc.isObjectFunction(data.classKey, 'prepareAnimation')){ data.classKey.prepareAnimation({data, uiScene, pack: this}); } } @@ -160,7 +160,7 @@ class PreloaderHandler this.createWithDirection(data, uiScene); } } - if(data.classKey && sc.isFunction(data.classKey, 'createAnimation')){ + if(data.classKey && sc.isObjectFunction(data.classKey, 'createAnimation')){ data.classKey.createAnimation({data, uiScene, pack: this}); } } diff --git a/lib/actions/server/player-enricher.js b/lib/actions/server/player-enricher.js index 33fd27dcc..fb28f8cd7 100644 --- a/lib/actions/server/player-enricher.js +++ b/lib/actions/server/player-enricher.js @@ -51,7 +51,7 @@ class PlayerEnricher { return async (target, executedSkill) => { let messageData = Object.assign({skillKey: executedSkill.key}, executedSkill.owner.getPosition()); - if(sc.isFunction(executedSkill.owner, 'getSkillExtraData')){ + if(sc.isObjectFunction(executedSkill.owner, 'getSkillExtraData')){ let params = {skill: executedSkill, target}; Object.assign(messageData, {extraData: executedSkill.owner.getSkillExtraData(params)}); } diff --git a/lib/config/constants.js b/lib/config/constants.js index 2a4777ae4..1ddd68ec6 100644 --- a/lib/config/constants.js +++ b/lib/config/constants.js @@ -8,8 +8,9 @@ // constants: module.exports.ConfigConst = { - CONFIG_TYPE_BOOLEAN: 'b', - CONFIG_TYPE_NUMBER: 'i', - CONFIG_TYPE_TEXT: 't', - CONFIG_TYPE_JSON: 'j' + CONFIG_TYPE_TEXT: 1, + CONFIG_TYPE_FLOAT: 2, + CONFIG_TYPE_BOOLEAN: 3, + CONFIG_TYPE_JSON: 4, + CONFIG_TYPE_COMMA_SEPARATED: 5 }; diff --git a/lib/config/server/manager.js b/lib/config/server/manager.js index 22e2d5e82..f8daa8d30 100644 --- a/lib/config/server/manager.js +++ b/lib/config/server/manager.js @@ -81,12 +81,12 @@ class ConfigManager extends ConfigProcessor { await this.events.emit('reldens.beforeGetParsedValue', {configManager: this, config: config}); if(config.type === ConfigConst.CONFIG_TYPE_TEXT){ - return config.value; + return config.value.toString(); } if(config.type === ConfigConst.CONFIG_TYPE_BOOLEAN){ return !(config.value === 'false' || config.value === '0'); } - if(config.type === ConfigConst.CONFIG_TYPE_NUMBER){ + if(config.type === ConfigConst.CONFIG_TYPE_FLOAT){ return parseFloat(config.value); } if(config.type === ConfigConst.CONFIG_TYPE_JSON){ @@ -96,6 +96,9 @@ class ConfigManager extends ConfigProcessor Logger.error('Invalid JSON on configuration:', config); } } + if(config.type === ConfigConst.CONFIG_TYPE_COMMA_SEPARATED){ + return config.value.split(','); + } return config.value; } diff --git a/lib/features/client/config-client.js b/lib/features/client/config-client.js index 8839da86c..827f21d8a 100644 --- a/lib/features/client/config-client.js +++ b/lib/features/client/config-client.js @@ -16,6 +16,7 @@ const { UsersPlugin } = require('../../users/client/plugin'); const { AudioPlugin } = require('../../audio/client/plugin'); const { RoomsPlugin } = require('../../rooms/client/plugin'); const { PredictionPlugin } = require('../../prediction/client/plugin'); +const { TeamsPlugin } = require('../../teams/client/plugin'); module.exports.ClientCoreFeatures = { chat: ChatPlugin, @@ -25,5 +26,6 @@ module.exports.ClientCoreFeatures = { users: UsersPlugin, audio: AudioPlugin, rooms: RoomsPlugin, - prediction: PredictionPlugin + prediction: PredictionPlugin, + teams: TeamsPlugin }; diff --git a/lib/features/server/config-server.js b/lib/features/server/config-server.js index 01dbbf34a..f3bc3676a 100644 --- a/lib/features/server/config-server.js +++ b/lib/features/server/config-server.js @@ -17,6 +17,7 @@ const { UsersPlugin } = require('../../users/server/plugin'); const { AudioPlugin } = require('../../audio/server/plugin'); const { RoomsPlugin } = require('../../rooms/server/plugin'); const { AdminPlugin } = require('../../admin/server/plugin'); +const { TeamsPlugin } = require('../../teams/server/plugin'); module.exports.ServerCoreFeatures = { chat: ChatPlugin, @@ -27,5 +28,6 @@ module.exports.ServerCoreFeatures = { users: UsersPlugin, audio: AudioPlugin, rooms: RoomsPlugin, - admin: AdminPlugin + admin: AdminPlugin, + teams: TeamsPlugin }; diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 97b91a80c..4729d58a0 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -243,7 +243,7 @@ class RoomEvents Logger.error('Listener "'+listenerKey+'" is missing.'); return false; } - if(!sc.isFunction(listener, 'executeClientMessageActions')){ + if(!sc.isObjectFunction(listener, 'executeClientMessageActions')){ Logger.error('Listener is missing "executeClientMessageActions" method.', listener); return false; } diff --git a/lib/game/client/ui-factory.js b/lib/game/client/ui-factory.js new file mode 100644 index 000000000..af0ac6b7f --- /dev/null +++ b/lib/game/client/ui-factory.js @@ -0,0 +1,54 @@ +/** + * + * Reldens - UiFactory + * + */ + +const { GameConst } = require('../constants'); +const { sc } = require('@reldens/utils'); + +class UiFactory +{ + + constructor(uiScene) + { + this.uiScene = uiScene; + this.gameManager = this.uiScene.gameManager; + } + + create(uiCodeName, depth, defaultOpen, defaultClose, openCallback, closeCallback) + { + let {uiX, uiY} = this.uiScene.getUiConfig(uiCodeName); + let newUiObject = this.uiScene.add.dom(uiX, uiY).createFromCache(uiCodeName); + let openButton = newUiObject.getChildByProperty('id', uiCodeName+GameConst.UI_OPEN); + let closeButton = newUiObject.getChildByProperty('id', uiCodeName+GameConst.UI_CLOSE); + openButton?.addEventListener('click', () => { + if(defaultOpen){ + let box = newUiObject.getChildByProperty('id', uiCodeName+'-ui'); + box.style.display = 'block'; + openButton.style.display = 'none'; + newUiObject.setDepth(depth); + } + if(openCallback && 'function' === typeof (openCallback)){ + openCallback(); + } + }); + closeButton?.addEventListener('click', () => { + if(defaultClose){ + let box = newUiObject.getChildByProperty('id', uiCodeName+'-ui'); + box.style.display = 'none'; + newUiObject.setDepth(1); + if(openButton){ + openButton.style.display = 'block'; + } + } + if(closeCallback && 'function' === typeof (closeCallback)){ + closeCallback(); + } + }); + this.uiScene.elementsUi[uiCodeName] = newUiObject; + } + +} + +module.exports.UiFactory = UiFactory; diff --git a/lib/game/constants.js b/lib/game/constants.js index 521d5508b..87f23739c 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -24,6 +24,8 @@ module.exports.GameConst = { GAME_OVER: 'go', REVIVED: 'rv', BUTTON_OPTION: 'btn-opt', + UI_CLOSE: '-close', + UI_OPEN: '-open', // movement: UP: 'up', LEFT: 'left', diff --git a/lib/inventory/client/inventory-ui.js b/lib/inventory/client/inventory-ui.js index 90960b9ac..25637d3c7 100644 --- a/lib/inventory/client/inventory-ui.js +++ b/lib/inventory/client/inventory-ui.js @@ -2,56 +2,35 @@ * * Reldens - InventoryUi * - * This class will handle the inventory UI and assign all the related events and actions. - * */ +const { UiFactory } = require('../../game/client/ui-factory'); const { InventoryConst } = require('../constants'); -class InventoryUi +class InventoryUi extends UiFactory { - constructor(uiScene) - { - this.uiScene = uiScene; - this.gameManager = this.uiScene.gameManager; - } - createUi() { - this.create('inventory', 5); - this.create('equipment', 4); + this.create('inventory', 5, true, true, null, () => { + this.inventoryVisibility('inventory'); + }); + this.create('equipment', 4, true, true, null, () => { + this.inventoryVisibility('inventory'); + }); } - create(codeName, depth) + inventoryVisibility(constantCodeName) { - let consName = codeName.toUpperCase(); - let {uiX, uiY} = this.uiScene.getUiConfig(codeName); - let newUiObject = this.uiScene.add.dom(uiX, uiY).createFromCache(codeName); - let closeButton = newUiObject.getChildByProperty('id', InventoryConst[consName+'_CLOSE']); - let openButton = newUiObject.getChildByProperty('id', InventoryConst[consName+'_OPEN']); - closeButton?.addEventListener('click', () => { - let box = newUiObject.getChildByProperty('id', codeName+'-ui'); - box.style.display = 'none'; - let uiPanel = newUiObject.getChildByProperty('id', InventoryConst[consName+'_ITEMS']); - uiPanel.querySelectorAll('.item-box .image-container img').forEach(function(element){ - element.style.border = 'none'; - }); - uiPanel.querySelectorAll('.item-data-container').forEach(function(element){ - element.style.display = 'none'; - }); - if(openButton){ - openButton.style.display = 'block'; - } - newUiObject.setDepth(1); - }); - openButton?.addEventListener('click', () => { - let box = newUiObject.getChildByProperty('id', codeName+'-ui'); - box.style.display = 'block'; - openButton.style.display = 'none'; - newUiObject.setDepth(depth); - }); - this.uiScene.elementsUi[codeName] = newUiObject; + let containerId = '#'+InventoryConst[constantCodeName+'_ITEMS']; + let itemImages = this.gameManager.gameDom.getElements(containerId+' .item-box .image-container img'); + for(let itemImage of itemImages){ + itemImage.style.border = 'none'; + } + let itemContainers = this.gameManager.gameDom.getElements(containerId+' .item-data-container') + for(let itemContainer of itemContainers){ + itemContainer.style.border = 'none'; + } } } diff --git a/lib/inventory/client/plugin.js b/lib/inventory/client/plugin.js index 6ec394c1c..39b3c2722 100644 --- a/lib/inventory/client/plugin.js +++ b/lib/inventory/client/plugin.js @@ -11,6 +11,7 @@ const { TradeTargetAction } = require('./exchange/trade-target-action'); const { TradeMessageListener } = require('./trade-message-listener'); const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); +const { TemplatesHandler } = require('./templates-handler'); const { InventoryConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); @@ -36,7 +37,7 @@ class InventoryPlugin extends PluginInterface this.onPlayerAdd(key, roomEvents, player); }); this.events.on('reldens.preloadUiScene', (preloadScene) => { - this.preloadTemplates(preloadScene); + TemplatesHandler.preloadTemplates(preloadScene); }); this.events.on('reldens.createUiScene', (preloadScene) => { return this.onPreloadUiScene(preloadScene); @@ -47,55 +48,37 @@ class InventoryPlugin extends PluginInterface this.gameManager.config.client.message.listeners['trade'] = new TradeMessageListener(); } - preloadTemplates(preloadScene) - { - // @TODO - BETA - Replace by loader replacing snake name file name by camel case for the template key. - let inventoryTemplatePath = 'assets/features/inventory/templates/'; - // @TODO - BETA - Move the preload HTML as part of the engine driver. - preloadScene.load.html('inventory', inventoryTemplatePath+'ui-inventory.html'); - preloadScene.load.html('equipment', inventoryTemplatePath+'ui-equipment.html'); - preloadScene.load.html('inventoryItem', inventoryTemplatePath+'item.html'); - preloadScene.load.html('inventoryItemUse', inventoryTemplatePath+'usable.html'); - preloadScene.load.html('inventoryItemEquip', inventoryTemplatePath+'equip.html'); - preloadScene.load.html('inventoryGroup', inventoryTemplatePath+'group.html'); - preloadScene.load.html('inventoryTradeContainer', inventoryTemplatePath+'trade-container.html'); - preloadScene.load.html('inventoryTradePlayerContainer', inventoryTemplatePath+'trade-player-container.html'); - preloadScene.load.html('inventoryTradeRequirements', inventoryTemplatePath+'trade-requirements.html'); - preloadScene.load.html('inventoryTradeRewards', inventoryTemplatePath+'trade-rewards.html'); - preloadScene.load.html('inventoryTradeAction', inventoryTemplatePath+'trade-action.html'); - preloadScene.load.html('inventoryTradeActionRemove', inventoryTemplatePath+'trade-action-remove.html'); - preloadScene.load.html('inventoryTradeItem', inventoryTemplatePath+'trade-item.html'); - preloadScene.load.html('inventoryTradeItemQuantity', inventoryTemplatePath+'trade-item-quantity.html'); - preloadScene.load.html('inventoryTradeStart', inventoryTemplatePath+'trade-start.html'); - preloadScene.load.html('inventoryTradeAccept', inventoryTemplatePath+'trade-accept.html'); - } - onPreloadUiScene(preloadScene) { this.uiManager = new InventoryUi(preloadScene); this.uiManager.createUi(); - let inventoryPanel = preloadScene.getUiElement('inventory') - .getChildByProperty('id', InventoryConst.INVENTORY_ITEMS); - let equipmentPanel = preloadScene.getUiElement('equipment') - .getChildByProperty('id', InventoryConst.EQUIPMENT_ITEMS); + let inventoryPanel = preloadScene.getUiElement('inventory').getChildByProperty( + 'id', + InventoryConst.INVENTORY_ITEMS + ); + let equipmentPanel = preloadScene.getUiElement('equipment').getChildByProperty( + 'id', + InventoryConst.EQUIPMENT_ITEMS + ); if(!inventoryPanel || !equipmentPanel){ Logger.error(['Inventory/Equipment UI not found.', inventoryPanel, equipmentPanel]); return false; } let manager = preloadScene.gameManager.inventory.manager; - // first time load, then we listen the events to get the updates: - if(Object.keys(manager.groups).length){ + let inventoryGroups = sc.get(manager, 'groups', {}); + if(Object.keys(inventoryGroups).length){ preloadScene.gameManager.gameDom.getElement('#' + InventoryConst.EQUIPMENT_ITEMS).innerHTML = ''; - let orderedGroups = this.sortGroups(manager.groups); + let orderedGroups = this.sortGroups(inventoryGroups); for(let i of orderedGroups){ - let output = this.createGroupBox(manager.groups[i], preloadScene.gameManager, preloadScene); + let output = this.createGroupBox(inventoryGroups[i], preloadScene.gameManager, preloadScene); preloadScene.gameManager.gameDom.appendToElement('#' + InventoryConst.EQUIPMENT_ITEMS, output); } } - let itemsKeys = Object.keys(manager.items); + let itemsElements = sc.get(manager, 'items', {}); + let itemsKeys = Object.keys(itemsElements); if(0 < itemsKeys.length){ for(let i of itemsKeys){ - let item = manager.items[i]; + let item = itemsElements[i]; this.displayItem(item, preloadScene, equipmentPanel, inventoryPanel, i); } } diff --git a/lib/inventory/client/templates-handler.js b/lib/inventory/client/templates-handler.js new file mode 100644 index 000000000..29a124f09 --- /dev/null +++ b/lib/inventory/client/templates-handler.js @@ -0,0 +1,35 @@ +/** + * + * Reldens - TemplatesHandler + * + */ + +class TemplatesHandler +{ + + static preloadTemplates(preloadScene) + { + // @TODO - BETA - Replace by loader replacing snake name file name by camel case for the template key. + let inventoryTemplatePath = 'assets/features/inventory/templates/'; + // @TODO - BETA - Move the preload HTML as part of the engine driver. + preloadScene.load.html('inventory', inventoryTemplatePath+'ui-inventory.html'); + preloadScene.load.html('equipment', inventoryTemplatePath+'ui-equipment.html'); + preloadScene.load.html('inventoryItem', inventoryTemplatePath+'item.html'); + preloadScene.load.html('inventoryItemUse', inventoryTemplatePath+'usable.html'); + preloadScene.load.html('inventoryItemEquip', inventoryTemplatePath+'equip.html'); + preloadScene.load.html('inventoryGroup', inventoryTemplatePath+'group.html'); + preloadScene.load.html('inventoryTradeContainer', inventoryTemplatePath+'trade-container.html'); + preloadScene.load.html('inventoryTradePlayerContainer', inventoryTemplatePath+'trade-player-container.html'); + preloadScene.load.html('inventoryTradeRequirements', inventoryTemplatePath+'trade-requirements.html'); + preloadScene.load.html('inventoryTradeRewards', inventoryTemplatePath+'trade-rewards.html'); + preloadScene.load.html('inventoryTradeAction', inventoryTemplatePath+'trade-action.html'); + preloadScene.load.html('inventoryTradeActionRemove', inventoryTemplatePath+'trade-action-remove.html'); + preloadScene.load.html('inventoryTradeItem', inventoryTemplatePath+'trade-item.html'); + preloadScene.load.html('inventoryTradeItemQuantity', inventoryTemplatePath+'trade-item-quantity.html'); + preloadScene.load.html('inventoryTradeStart', inventoryTemplatePath+'trade-start.html'); + preloadScene.load.html('inventoryTradeAccept', inventoryTemplatePath+'trade-accept.html'); + } + +} + +module.exports.TemplatesHandler = TemplatesHandler; diff --git a/lib/inventory/server/message-actions.js b/lib/inventory/server/message-actions.js index 1c242b02a..7b48ffa4e 100644 --- a/lib/inventory/server/message-actions.js +++ b/lib/inventory/server/message-actions.js @@ -25,7 +25,7 @@ class InventoryMessageActions if(!sc.hasOwn(data, 'id') || !data.id){ return false; } - if(!sc.isFunction(data.id, 'indexOf') || 0 !== data.id.indexOf('trade')){ + if(!sc.isObjectFunction(data.id, 'indexOf') || 0 !== data.id.indexOf('trade')){ return false; } return this.closeTradeAction(client, data, room, playerSchema); @@ -149,7 +149,7 @@ class InventoryMessageActions { let subActionParam = sc.get(data, ObjectsConst.TRADE_ACTIONS.SUB_ACTION, false); let mappedSubAction = this.mapSubAction(subActionParam); - if(false === mappedSubAction || !sc.isFunction(PlayerProcessor, mappedSubAction)){ + if(false === mappedSubAction || !sc.isObjectFunction(PlayerProcessor, mappedSubAction)){ Logger.critical('Missing mapped sub-action.', mappedSubAction); return false; } diff --git a/lib/objects/server/manager.js b/lib/objects/server/manager.js index 56cee3088..cb1b2596e 100644 --- a/lib/objects/server/manager.js +++ b/lib/objects/server/manager.js @@ -111,7 +111,7 @@ class ObjectsManager async runAdditionalSetup(objectInstance, objectData) { - if(!sc.isFunction(objectInstance, 'runAdditionalSetup')){ + if(!sc.isObjectFunction(objectInstance, 'runAdditionalSetup')){ return false; } objectInstance.runAdditionalSetup({objectsManager: this, objectData}); diff --git a/lib/objects/server/object/type/enemy-object.js b/lib/objects/server/object/type/enemy-object.js index c3e6b8fe9..5e74bb7d9 100644 --- a/lib/objects/server/object/type/enemy-object.js +++ b/lib/objects/server/object/type/enemy-object.js @@ -162,7 +162,7 @@ class EnemyObject extends NpcObject }, executedSkill.owner.getPosition() ); - if(sc.isFunction(executedSkill.owner, 'getSkillExtraData')){ + if(sc.isObjectFunction(executedSkill.owner, 'getSkillExtraData')){ let params = {skill: executedSkill, target}; Object.assign(messageData, {extraData: executedSkill.owner.getSkillExtraData(params)}); } diff --git a/lib/objects/server/object/type/trader-object.js b/lib/objects/server/object/type/trader-object.js index 397b7139e..9376d651e 100644 --- a/lib/objects/server/object/type/trader-object.js +++ b/lib/objects/server/object/type/trader-object.js @@ -142,7 +142,7 @@ class TraderObject extends NpcObject } let subActionParam = sc.get(data, ObjectsConst.TRADE_ACTIONS.SUB_ACTION, false); let mappedSubAction = this.mapSubAction(subActionParam); - if(false !== mappedSubAction && sc.isFunction(Processor, mappedSubAction)){ + if(false !== mappedSubAction && sc.isObjectFunction(Processor, mappedSubAction)){ return await this.processSubAction(mappedSubAction, tradeKey, data, playerSchema, inventoryKey, tradeAction, client); } return await this.initializeTransaction(tradeKey, data, playerSchema, inventoryKey, tradeAction, client); diff --git a/lib/respawn/server/room-respawn.js b/lib/respawn/server/room-respawn.js index dfa7ef422..f4c0d9beb 100644 --- a/lib/respawn/server/room-respawn.js +++ b/lib/respawn/server/room-respawn.js @@ -74,7 +74,7 @@ class RoomRespawn // add tile data to the object and create object instance: Object.assign(clonedObjProps, tileData); let objInstance = new objClass(clonedObjProps); - if(sc.isFunction(objInstance, 'runAdditionalRespawnSetup')){ + if(sc.isObjectFunction(objInstance, 'runAdditionalRespawnSetup')){ await objInstance.runAdditionalRespawnSetup(); } this.events.emit('reldens.afterRunAdditionalRespawnSetup', { diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js new file mode 100644 index 000000000..eaf00669e --- /dev/null +++ b/lib/teams/client/plugin.js @@ -0,0 +1,89 @@ +/** + * + * Reldens - Teams Client Plugin + * + */ + +const { UserInterface } = require('../../game/client/user-interface'); +const { PluginInterface } = require('../../features/plugin-interface'); +const { TeamTargetActions } = require('./team-create-target-action'); +const { Logger, sc } = require('@reldens/utils'); + +class TeamsPlugin extends PluginInterface +{ + + setup(props) + { + this.teamTargetActions = new TeamTargetActions(); + this.gameManager = sc.get(props, 'gameManager', false); + // @NOTE: the tradeUi works as preload for the trade template which at the end is a dialog-box. + this.teamsUi = new UserInterface(this.gameManager, {id: 'trade', type: 'trade'}); + if(!this.gameManager){ + Logger.error('Game Manager undefined in InventoryPlugin.'); + } + this.events = sc.get(props, 'events', false); + if(!this.events){ + Logger.error('EventsManager undefined in InventoryPlugin.'); + } + this.events.on('reldens.preloadUiScene', (preloadScene) => { + this.preloadTemplates(preloadScene); + }); + this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { + this.teamTargetActions.showTeamInviteAction(this.gameManager, target, previousTarget, targetName); + }); + this.events.on('reldens.playersOnAdd', (player, key, previousScene, roomEvents) => { + this.onPlayerAdd(key, roomEvents, player); + }); + /* + this.events.on('reldens.createUiScene', (preloadScene) => { + return this.onPreloadUiScene(preloadScene); + }); + this.gameManager.config.client.message.listeners['trade'] = new TradeMessageListener(); + */ + } + + preloadTemplates(preloadScene) + { + let teamsTemplatePath = 'assets/features/teams/templates/'; + preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); + preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); + } + + onPreloadUiScene(preloadScene) + { + /* + this.uiManager = new InventoryUi(preloadScene); + this.uiManager.createUi(); + let inventoryPanel = preloadScene.getUiElement('inventory').getChildByProperty( + 'id', + InventoryConst.INVENTORY_ITEMS + ); + let equipmentPanel = preloadScene.getUiElement('equipment').getChildByProperty( + 'id', + InventoryConst.EQUIPMENT_ITEMS + ); + if(!inventoryPanel || !equipmentPanel){ + Logger.error(['Inventory/Equipment UI not found.', inventoryPanel, equipmentPanel]); + return false; + } + let manager = preloadScene.gameManager.inventory.manager; + // listen for inventory events: + this.listenInventoryEvents(preloadScene, inventoryPanel, equipmentPanel); + */ + } + + onPlayerAdd(key, roomEvents, player) + { + if(key !== roomEvents.room.sessionId){ + return false; + } + } + + listenInventoryEvents(uiScene, inventoryPanel, equipmentPanel) + { + + } + +} + +module.exports.TeamsPlugin = TeamsPlugin; diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js new file mode 100644 index 000000000..5ea2da0a9 --- /dev/null +++ b/lib/teams/client/team-create-target-action.js @@ -0,0 +1,42 @@ +/** + * + * Reldens - TradeTargetAction + * + */ + +const { TeamsConst } = require('../constants'); +const { GameConst } = require('../../game/constants'); +const { sc } = require('@reldens/utils'); + +class TeamTargetActions +{ + + showTeamInviteAction(gameManager, target, previousTarget, targetName) + { + if(GameConst.TYPE_PLAYER !== target.type || gameManager.getCurrentPlayer().playerId === target.id){ + return false; + } + let uiScene = gameManager.gameEngine.uiScene; + let uiTarget = sc.get(uiScene, 'uiTarget', false); + if(false === uiTarget){ + return false; + } + let teamPlayerActionsTemplate = uiScene.cache.html.get('teamPlayerInvite'); + uiTarget.getChildByID('box-target').style.display = 'block'; + uiTarget.getChildByID('target-container').innerHTML += gameManager.gameEngine.parseTemplate( + teamPlayerActionsTemplate, + { + playerName: targetName, + playerId: target.id + } + ); + gameManager.gameDom.getElement('.team-invite-'+target.id+' button')?.addEventListener('click', () => { + let sendData = {act: TeamsConst.ACTIONS.TEAM_INVITE, id: target.id}; + console.log({sendData}); + gameManager.room.send('*', sendData); + }); + } + +} + +module.exports.TeamTargetActions = TeamTargetActions; \ No newline at end of file diff --git a/lib/teams/constants.js b/lib/teams/constants.js new file mode 100644 index 000000000..bbda47550 --- /dev/null +++ b/lib/teams/constants.js @@ -0,0 +1,11 @@ +/** + * + * Reldens - teams/constants + * + */ + +module.exports.TeamsConst = { + ACTIONS: { + TEAM_INVITE: 't.inv' + } +}; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js new file mode 100644 index 000000000..c076cbfec --- /dev/null +++ b/lib/teams/server/plugin.js @@ -0,0 +1,30 @@ +/** + * + * Reldens - Teams Server Plugin. + * + */ + +const { PluginInterface } = require('../../features/plugin-interface'); +const { Logger, sc } = require('@reldens/utils'); + +class TeamsPlugin extends PluginInterface +{ + + setup(props) + { + this.events = sc.get(props, 'events', false); + if(!this.events){ + Logger.error('EventsManager undefined in TeamsPlugin.'); + } + this.events.on('reldens.beforeSuperInitialGameData', async (superInitialGameData, roomGame) => { + await this.onBeforeSuperInitialGameData(superInitialGameData, roomGame); + }); + } + + async onBeforeSuperInitialGameData(superInitialGameData, roomGame) + { + } + +} + +module.exports.TeamsPlugin = TeamsPlugin; diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql new file mode 100644 index 000000000..b048053b1 --- /dev/null +++ b/migrations/production/beta.26-sql-update.sql @@ -0,0 +1,75 @@ +####################################################################################################################### + +SET FOREIGN_KEY_CHECKS = 0; + +####################################################################################################################### + +# Config Types: + +CREATE TABLE `config_types` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `label` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci', + PRIMARY KEY (`id`) USING BTREE +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + +INSERT INTO `config_types` VALUES (1, 'string'); +INSERT INTO `config_types` VALUES (2, 'float'); +INSERT INTO `config_types` VALUES (3, 'boolean'); +INSERT INTO `config_types` VALUES (4, 'json'); +INSERT INTO `config_types` VALUES (5, 'comma_separated'); + +UPDATE `config` SET `type` = 't' WHERE `path` = 'actions/pvp/timerType'; + +SET @string_id = (SELECT `id` FROM `config_types` WHERE `label` = 'string'); +SET @boolean_id = (SELECT `id` FROM `config_types` WHERE `label` = 'boolean'); +SET @float_id = (SELECT `id` FROM `config_types` WHERE `label` = 'float'); +SET @json_id = (SELECT `id` FROM `config_types` WHERE `label` = 'json'); +SET @comma_separated_id = (SELECT `id` FROM `config_types` WHERE `label` = 'comma_separated'); + +UPDATE `config` SET `type` = @string_id WHERE `type` = 't'; +UPDATE `config` SET `type` = @boolean_id WHERE `type` = 'b'; +UPDATE `config` SET `type` = @float_id WHERE `type` = 'i'; +UPDATE `config` SET `type` = @json_id WHERE `type` = 'j'; +UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; + +ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; +ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; + +# Config: +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/enabled', '1', @boolean_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveX', '5', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '5', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '5', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '5', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', 'stats/hp,stats/mp', @comma_separated_id); + +# Features: +INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); + +# Clan and members: + +CREATE TABLE `clan` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `owner_id` INT(10) UNSIGNED NOT NULL, + `name` VARCHAR(50) NOT NULL DEFAULT '' COLLATE 'utf8_unicode_ci', + `points` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `level` INT(10) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `owner_id` (`owner_id`) USING BTREE, + UNIQUE INDEX `name` (`name`) USING BTREE +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + +CREATE TABLE `clan_members` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `clan_id` INT(10) UNSIGNED NOT NULL, + `player_id` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `FK__clan` (`clan_id`) USING BTREE, + INDEX `FK__players` (`player_id`) USING BTREE +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + +####################################################################################################################### + +SET FOREIGN_KEY_CHECKS = 1; + +####################################################################################################################### diff --git a/package-lock.json b/package-lock.json index b97e347f6..812c428f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "reldens", - "version": "4.0.0-beta.25", + "version": "4.0.0-beta.26", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "reldens", - "version": "4.0.0-beta.25", + "version": "4.0.0-beta.26", "license": "MIT", "dependencies": { "@adminjs/design-system": "2.1.2", @@ -53,11 +53,11 @@ "@parcel/transformer-svg": "2.8.0", "@parcel/transformer-webmanifest": "2.8.0", "@parcel/transformer-worklet": "2.8.0", - "@reldens/items-system": "^0.16.3", - "@reldens/modifiers": "^0.17.1", - "@reldens/skills": "^0.16.6", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.8", + "@reldens/items-system": "^0.17.0", + "@reldens/modifiers": "^0.18.0", + "@reldens/skills": "^0.17.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", @@ -146,9 +146,9 @@ } }, "node_modules/@aws-crypto/ie11-detection": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", - "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, "dependencies": { "tslib": "^1.11.1" @@ -161,16 +161,16 @@ "optional": true }, "node_modules/@aws-crypto/sha256-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", - "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, "dependencies": { - "@aws-crypto/ie11-detection": "^2.0.0", - "@aws-crypto/sha256-js": "^2.0.0", - "@aws-crypto/supports-web-crypto": "^2.0.0", - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" @@ -183,13 +183,13 @@ "optional": true }, "node_modules/@aws-crypto/sha256-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", - "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, "dependencies": { - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, @@ -200,9 +200,9 @@ "optional": true }, "node_modules/@aws-crypto/supports-web-crypto": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", - "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, "dependencies": { "tslib": "^1.11.1" @@ -215,12 +215,12 @@ "optional": true }, "node_modules/@aws-crypto/util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", - "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, "dependencies": { - "@aws-sdk/types": "^3.110.0", + "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } @@ -232,12 +232,12 @@ "optional": true }, "node_modules/@aws-sdk/abort-controller": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz", - "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.254.0.tgz", + "integrity": "sha512-ZBJFCCU7mIXGLk5GFXrSReyUR/kOBju0kzd7nVAAQQlfkmHZEuFhKFFMXkfJZG0SC0ezCbmR/EzIqJ2mTI+pRA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -245,44 +245,44 @@ } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.238.0.tgz", - "integrity": "sha512-vjIVFzlkcUX9YT7pqg9NrvOTuqqsdYSesFgMLTCSWXBE5rPjWEK5ljIVOZtGn8MJQgwqS+Lqc8oB1vIfE+EaSA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/client-sts": "3.238.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.254.0.tgz", + "integrity": "sha512-FZlTQqgY7v3A2SPq0wR+W1ApJ5hSB0qYezDfaDofpdZbHFKLKyQQr14n5PBrT57/I9Wo75rrgPLENhAqOu3TfQ==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.254.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" @@ -292,41 +292,41 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.238.0.tgz", - "integrity": "sha512-KHJJWP7hBDa9KLYiU5+hOb+3AAba93PhWebXkpKyQ/Bs+e7ECCreyLCwuME6uWTV01NDuFDpwZ6zUMpyNIcP6Q==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.254.0.tgz", + "integrity": "sha512-Ih80wJpGHa4nwxtTQvmSTT1UXQeHweYk+A6y779H1dtczr8h9Y2lXZa0C0TGcd1LEpL0nYHw0kJE3ZBw1/0nhw==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" @@ -336,41 +336,41 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.238.0.tgz", - "integrity": "sha512-kazcA2Kp+cXQRtaZi5/T5YFfU9J3nzu1tXJsh0xAm+J3S9LS1ertY1bSX6KBed2xuxx2mfum8JRqli0TJad/pA==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.254.0.tgz", + "integrity": "sha512-KRF/hBysJgrZpjRgg47Fa7YzC10Ypqf/FSeesJkN6l2lULosO+o0N+RD4t5LLWXrD10c9by6m1ueC6077z0fGQ==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" @@ -380,44 +380,44 @@ } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.238.0.tgz", - "integrity": "sha512-jQNwHqxWUGvWCN4o8KUFYQES8r41Oobu7x1KZOMrPhPxy27FUcDjBq/h85VoD2/AZlETSCZLiCnKV3KBh5pT5w==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-sdk-sts": "3.226.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.254.0.tgz", + "integrity": "sha512-up+u5ik1pZ9Vo2MtcMCZ6pNAFhrePbH/IBFSgSsJlCU4AsGxPBsm59CE3/n/e84pFzhwVmFEUdavysIM+aP8LA==", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-sdk-sts": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "fast-xml-parser": "4.0.11", @@ -428,15 +428,15 @@ } }, "node_modules/@aws-sdk/config-resolver": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz", - "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.254.0.tgz", + "integrity": "sha512-+t5mi/SrZdAbSgg/5b/q3zVZsNQSyty2XX+znaRvBdANtIWIBdFLEMQp/L5NA+PSiW6VUXu9eXcsj0kJlAhTgQ==", "optional": true, "dependencies": { - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -444,14 +444,14 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.238.0.tgz", - "integrity": "sha512-FGVXGtGn7t/XacBdCKgqzxJNvorjCIar+F0oRsGq8aH09L/H8uGZ7qXvYvi9I94Qv7ay0Dy/KpGcMB53zt41UA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.254.0.tgz", + "integrity": "sha512-aZgGDn7lutcLcYp8z6z3PvfoGvrudv5mYj7mf/HDcEiHwpHGu+1t5qUw7VVD4e5yd3OCGJ2Rttohw9EIvXgcmg==", "optional": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-cognito-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -459,13 +459,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz", - "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.254.0.tgz", + "integrity": "sha512-2CDwb7L1XGTY7Y8N3EsE1xqas0zNvrs4aOEv5XZNrKqE+9bvs8CiUwV4SB6VwSD+EPcOSm3QYEURUmj5EyLEZQ==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -473,15 +473,15 @@ } }, "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz", - "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.254.0.tgz", + "integrity": "sha512-sM3N7FLz+svRGjTgwAybKBmu5tVfCJmd5HPEfKR0jfBWB1uq0u0J+65JiO/wfqn/ix+3ZyFfacSJDFjnSPu/KA==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -489,19 +489,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.238.0.tgz", - "integrity": "sha512-WmPNtIYyUasjV7VQxvPNq7ihmx0vFsiKAtjNjjakdrt5TPoma4nUYb9tIG9SuG+kcp4DJIgRLJAgZtXbCcVimg==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.254.0.tgz", + "integrity": "sha512-cqzrvuniurfiKmJsOlGyagUUwrZuOnEAxGDL0Olg5Ac3XByAO5AWH/mjy9P7u31j3vxybMz/Uw5kR7lV1q5sXQ==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -509,20 +509,20 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.238.0.tgz", - "integrity": "sha512-/RN5EyGfgdIIJdFzv+O0nSaHX1/F3anQjTIBeVg8GJ+82m+bDxMdALsG+NzkYnLilN9Uhc1lSNjLBCoPa5DSEg==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.238.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.254.0.tgz", + "integrity": "sha512-jjR/qLn0lwDJmeWwTMwWG2zk5GpjCdKts1Taxd5GFHHSzkH/FZG8Vr8u5yWRtoE/x876n+ItiRfcSrLTTwqkUQ==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-ini": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -530,14 +530,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz", - "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.254.0.tgz", + "integrity": "sha512-vNm1AHMu5Lg1kOMk4ucWgaNO4zNAD7aeRssdBMnC7WqRT2xB8CUEWi+zJGNjbxzEeTLXQZuMa1VeRT3nPjYrzg==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -545,16 +545,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.238.0.tgz", - "integrity": "sha512-i70V4bFlCVYey3QARJ6XxKEg/4YuoFRnePV2oK37UHOGpEn49uXKwVZqLjzJgFHln7BPlC06cWDqrHUQIMvYrQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.254.0.tgz", + "integrity": "sha512-uFfAQ/sIWreDA79HpJqdL+LkVp/yGkdBrDhjbiXIyeVWy/NmQNKu8l0j+wGazk1r562TJbzZ0Gz6+wTsdUSPgA==", "optional": true, "dependencies": { - "@aws-sdk/client-sso": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/token-providers": "3.238.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-sso": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/token-providers": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -562,13 +562,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz", - "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.254.0.tgz", + "integrity": "sha512-R/5qjAoCHEe7xmY5j0vges4xKpFpTgrwzdST822JVNWUobZmiDUqnn+1Xw4Qmomst625NOpgzsV4JuHsA4a8Ig==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -576,25 +576,25 @@ } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.238.0.tgz", - "integrity": "sha512-wqj88z9UCbqxnd9XoaKrI+7oSN/13f1AP7cZWYKrzdvb3Ae5QmXmgMKXJdsQlWtPijchpPOQNGhd1rVogycgiA==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.238.0", - "@aws-sdk/client-sso": "3.238.0", - "@aws-sdk/client-sts": "3.238.0", - "@aws-sdk/credential-provider-cognito-identity": "3.238.0", - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.238.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.254.0.tgz", + "integrity": "sha512-HoBfuSGU7cJ/My1gDLLePLlNjI/bAHQV+Ch8yqkipesfVuiO6aRaB7ipJYZS5OFJffkY9oJkZmGtqmrpfpziMQ==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.254.0", + "@aws-sdk/client-sso": "3.254.0", + "@aws-sdk/client-sts": "3.254.0", + "@aws-sdk/credential-provider-cognito-identity": "3.254.0", + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-ini": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -602,26 +602,27 @@ } }, "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz", - "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.254.0.tgz", + "integrity": "sha512-/bbtNHe5JHFdKnCVr3Zx55sqs4c0F+7f1CC5cvTgH3O46wgIRM/6/rvE0YieXmfm3ho/GOhxBUzy59A0haKQGg==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/querystring-builder": "3.254.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "tslib": "^2.3.1" } }, "node_modules/@aws-sdk/hash-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz", - "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.254.0.tgz", + "integrity": "sha512-7FoB6BVbO+Z/NEOHeOAoUTyj8q+Pcdn4QpKvA4epRDrzMNcXy7MUNzzt148nkDssES09rgsN+KM8Zo2qgRYngg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-buffer-from": "3.208.0", + "@aws-sdk/util-utf8": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -629,12 +630,12 @@ } }, "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz", - "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.254.0.tgz", + "integrity": "sha512-ueV0tXyGndCTZXnEv+AMeTfu+IqV2QzmGMXcakiwxDjg48H9X/bLnj+C96Sexond8jD8K0ub9HWhkBrvvAXlPA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, @@ -651,13 +652,13 @@ } }, "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz", - "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.254.0.tgz", + "integrity": "sha512-IT7nDZA6WsaZSNp9M79xfkk/us4kGV4SIZ2R9gHT9MFqdmpmbr3EGhFLKXUHcAZfCcOdw+JNV/wHJiiN1JD/hg==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -665,18 +666,18 @@ } }, "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz", - "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.254.0.tgz", + "integrity": "sha512-9fkDtSJdhEr91tWp4zLyKhHDGVyvUA0gDK+6wGYyorKCae2qX2TL+Fl6vsqY4PxrdTpXRBJDlJnEly9i48YKxg==", "optional": true, "dependencies": { - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -684,13 +685,13 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz", - "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.254.0.tgz", + "integrity": "sha512-JG+OoyCMivnqTYiPZxRF+sgYEyQG68+PMl2843owvSxQQ25nH2Ih6DzLqH10c/uAN0PsiA8s/FfJBzhw9Xf0KA==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -698,12 +699,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz", - "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.254.0.tgz", + "integrity": "sha512-h3jEw58VUJkfqrwWMmp3Qc8293RFo4LMqxNAVsVwYEG6xb/RQ+JamsOx+t6aDsoOdKqhYngWwDGtgUZQ5wQQvg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -711,13 +712,13 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz", - "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.254.0.tgz", + "integrity": "sha512-/4tTvAXmIIMCs3giPIXN9aVJUGMoBMWw+9WS22u7nYNzwTe/k30DhS91uvwj7TLOOpFN0IBNXPCJ+T1OZn+ZXQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -725,16 +726,16 @@ } }, "node_modules/@aws-sdk/middleware-retry": { - "version": "3.235.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz", - "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.254.0.tgz", + "integrity": "sha512-nHgris8NmtLzsH5iUA8geW6RAT1VRymjlieKFmM3CAYt2h2X8AtAiL/Wod+Pj3+jjRGk9YeGzOOGbzODHiRxnA==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/service-error-classification": "3.229.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/service-error-classification": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/util-middleware": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", "tslib": "^2.3.1", "uuid": "^8.3.2" }, @@ -743,16 +744,16 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz", - "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.254.0.tgz", + "integrity": "sha512-Y074nmTp07thuOI6GePv8IKdL/OvkO1tn2l7QvnwQa3Sy/HyNai1V3MVtq4hRi1dgDjheKPVHPE+TnOmF3w5uA==", "optional": true, "dependencies": { - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -760,12 +761,12 @@ } }, "node_modules/@aws-sdk/middleware-serde": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz", - "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.254.0.tgz", + "integrity": "sha512-YuItb2nlKADTBItcn68eA8amX4quuR1+0GyFRkwssKS/iTjbIk+3gJ2s1zxkUhlyozH3U38Jvvqd+W9+gNpYIg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -773,16 +774,16 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz", - "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.254.0.tgz", + "integrity": "sha512-HMVGf+yANjlKCUMFZJU2PNzbI9hbCgL+IX/Y4DGuQW9cp7EgZOxQre1LBKpcCqqPVQ4toIdfNH/K8uM2fpO6dg==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -790,9 +791,9 @@ } }, "node_modules/@aws-sdk/middleware-stack": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz", - "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.254.0.tgz", + "integrity": "sha512-yPWRnjeLC0lPAEQbiqbC3+hnqXZ+uCSoSevGndU5KWMMiXLxKZn7Y0B3kG8NAnNNuPid+wYFWWU9rKiBRvWR/w==", "optional": true, "dependencies": { "tslib": "^2.3.1" @@ -802,13 +803,13 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz", - "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.254.0.tgz", + "integrity": "sha512-hp5UYRg3ysZXMFMv34nYexyom6Z3pdx+OmisJz4w3AMigT8y57Ps30Vg+1QYaGlQkI4vfvcmdZX2Q+kp+mb9gQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -816,14 +817,14 @@ } }, "node_modules/@aws-sdk/node-config-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz", - "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.254.0.tgz", + "integrity": "sha512-3Bp3Gp2NOY9gab738xf07TysO5iB0Ib9qRNGDlxX8SX8fZDRnxrF2cn+Tjte42wrO54orwhSyuTaIlAqKeii8Q==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -831,15 +832,15 @@ } }, "node_modules/@aws-sdk/node-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz", - "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.254.0.tgz", + "integrity": "sha512-DX2WJ3pub+3FF9GpoF5doERCn06MxS/UmmbKnIIokWQHjPZVomNh/1P3Cf9Jn9jeIPgh4UOg0uPD8cUm/cwHQw==", "optional": true, "dependencies": { - "@aws-sdk/abort-controller": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/abort-controller": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/querystring-builder": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -847,12 +848,12 @@ } }, "node_modules/@aws-sdk/property-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz", - "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.254.0.tgz", + "integrity": "sha512-BLZF/LDFjAgv2ZY0vhThU58k++Aw+SK7qNU7XT0D84q5iWlYRKptQEvSSvIkBSI/rZoppOFhK7W80I8kNNbh+Q==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -860,12 +861,12 @@ } }, "node_modules/@aws-sdk/protocol-http": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz", - "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.254.0.tgz", + "integrity": "sha512-4o/I/qhMUTp70njwWe3ttyRJSAKegnr8l3oVWAf1/q1ZHpcxbRRZEDvrkx4KSunFeXTTGHcff1oyLSRG/cKMsQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -873,12 +874,12 @@ } }, "node_modules/@aws-sdk/querystring-builder": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz", - "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.254.0.tgz", + "integrity": "sha512-Er+pOGTrPxelrzggibduO+eB1ClaU2BhjA8gd0nORS3kqktQggG3tKmRSIilegi9WOa3awCk6CnnuAf0pBrbUA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-uri-escape": "3.201.0", "tslib": "^2.3.1" }, @@ -887,12 +888,12 @@ } }, "node_modules/@aws-sdk/querystring-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz", - "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.254.0.tgz", + "integrity": "sha512-WwRD99dwGo2aIrRjLHUAXaWCZ+3fj88IhIwciWTqrHBS3TQWXllOOQmYo7f+aMBB4Q1K6KdKITNi8L7aUuDv2g==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -900,21 +901,21 @@ } }, "node_modules/@aws-sdk/service-error-classification": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz", - "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.254.0.tgz", + "integrity": "sha512-8GHqMJBBF9yoMBG/Nf9PusUSMFjG8ygps/cSJPlgcG2vbFn8BCdBZVc4ptXqICZUnBB/6lrxy8nCmNUaru48jg==", "optional": true, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz", - "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.254.0.tgz", + "integrity": "sha512-UH4YTXuG+q004vA+jNrVhrD5XQCIAgpL/eriObJnQpKUVef1mkkEDHZs8+8+ZPsk4p/iBrIJ3lXNf7iDA/BFzw==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -922,16 +923,17 @@ } }, "node_modules/@aws-sdk/signature-v4": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz", - "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.254.0.tgz", + "integrity": "sha512-9FoEnipA9hAgEp6oqIT3+hobF+JgIXIn5QV8kAB7QGxEDqs/pdpEbGc9qbxi0ghdjvqzOSDir9gNI3w0cL8Aug==", "optional": true, "dependencies": { "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "@aws-sdk/util-uri-escape": "3.201.0", + "@aws-sdk/util-utf8": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -939,13 +941,13 @@ } }, "node_modules/@aws-sdk/smithy-client": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz", - "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.254.0.tgz", + "integrity": "sha512-SI0jz9JfWi1IaakDX/26xliKTIMJpzwwDoyQPEfZ/L0KKdpr2gNhljA3sR2pZ2EM1oqOaXpMHAunSzv7EBpBWg==", "optional": true, "dependencies": { - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -953,15 +955,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.238.0.tgz", - "integrity": "sha512-vYUwmy0kTzA99mJCVvad+/5RDlWve/xxnppT8DJK3JIdAgskp+rULY+joVnq2NSl489UAioUnFGs57vUxi8Pog==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.254.0.tgz", + "integrity": "sha512-i3W+YWrMtgdFPDWW/m56xrkBhqrB6beKgQi46oSM/aFZ3ZAkFJLfbsLK99LWzVxtzELTSFjJWY54r+Au/hb2kQ==", "optional": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-sso-oidc": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -969,9 +971,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz", - "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.254.0.tgz", + "integrity": "sha512-xDEDk6ZAGFO0URPgB6R2mvQANYlojHLjLC9zzOzl07F+uqYS30yZDIg4UFcqPt/x48v7mxlKZpbaZgYI2ZLgGA==", "optional": true, "dependencies": { "tslib": "^2.3.1" @@ -981,13 +983,13 @@ } }, "node_modules/@aws-sdk/url-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz", - "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.254.0.tgz", + "integrity": "sha512-Za0JGUa9p5GQ8t2tVtKaRSjLUxrmEdnBlUiZ2zKm86wFxgQnjbMwzD3mvyJ5OaVsXScU5vzc3CXHIXSvS7h7Ng==", "optional": true, "dependencies": { - "@aws-sdk/querystring-parser": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/querystring-parser": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, @@ -1051,13 +1053,13 @@ } }, "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz", - "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.254.0.tgz", + "integrity": "sha512-vj/s+BuqNKTHN9bsZ/HY7vpBWbo3F+4c3/ZoKSZa5Jc7jAuGCbx3zWwHdJFDgvbqLvsTBw80Q9d/CDy9pKj/tQ==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "bowser": "^2.11.0", "tslib": "^2.3.1" }, @@ -1066,16 +1068,16 @@ } }, "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz", - "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.254.0.tgz", + "integrity": "sha512-gvD2+Uf60c2BgUYv2d6R4dSpO/CbvybqblgF8lKZCsHkDWzfEdPv9nlJgUWM1cuMKQ0hBZ3cL3ilOwVKRVPyiQ==", "optional": true, "dependencies": { - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -1083,12 +1085,12 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.226.0.tgz", - "integrity": "sha512-iqOkac/zLmyPBUJd7SLN0PeZMkOmlGgD5PHmmekTClOkce2eUjK9SNX1PzL73aXPoPTyhg9QGLH8uEZEQ8YUzg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.254.0.tgz", + "integrity": "sha512-BzBIOnhVrs4RFTpGZErZfAV1VhqWglxn047VYijmCQe8Aejq4mJAaepSwHYar++XC0+pduD5YO8IidW8z/1vQQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -1120,9 +1122,9 @@ } }, "node_modules/@aws-sdk/util-middleware": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz", - "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.254.0.tgz", + "integrity": "sha512-gn7vInNTRBo2QatOB+uU99JwV53wf/zlTUnUK0qOuebtSDLMdiO+msiMi2ctz9vMIrtc2XMXNQro1aE0aUPy4w==", "optional": true, "dependencies": { "tslib": "^2.3.1" @@ -1132,12 +1134,12 @@ } }, "node_modules/@aws-sdk/util-retry": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz", - "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.254.0.tgz", + "integrity": "sha512-IVA4wAOJpVssEIbJmeq1fdDYvrkOqYFK9Pz4tERmMz33003fyY92dU468Lulw8MnsSALYiwWUoWSFg9L5RCTug==", "optional": true, "dependencies": { - "@aws-sdk/service-error-classification": "3.229.0", + "@aws-sdk/service-error-classification": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -1157,24 +1159,24 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz", - "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.254.0.tgz", + "integrity": "sha512-2HvwH8l7ln4qTDsU3rgH9NvSSo5qhX+2Lenb6XvNnIMkL4r/tPhNIaGKtoQRfpzLH378Mm9XEQnJM5UXFRWuTA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "bowser": "^2.11.0", "tslib": "^2.3.1" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz", - "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.254.0.tgz", + "integrity": "sha512-6nc9bmRP+2JqbBJ5oRZZRU8l35X3VcWF5j8XvmamWjIABsanc6Gv6NV4qAa3imPjIyWNiShZn/YkTBYs1exsdg==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" }, "engines": { @@ -1189,6 +1191,19 @@ } } }, + "node_modules/@aws-sdk/util-utf8": { + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", + "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", + "optional": true, + "dependencies": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/util-utf8-browser": { "version": "3.188.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", @@ -4077,16 +4092,16 @@ } }, "node_modules/@mikro-orm/core": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.2.tgz", - "integrity": "sha512-FDKJBL1gjT9d36E9dyC5S9OxoeEQzy7lIj/SpijU8BDDC6gpn27HJ00DLqG+gLdepXBYdDdf3B/ELi+OGxZKCw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.7.tgz", + "integrity": "sha512-XLCY3S10nz7uUFGTFypUuaO48cQAhCUiSsQBxpqqhlOqVz6yxGYdqAe7SmMKuplmpOoVL8OGDZrULzd0bIjtYA==", "dependencies": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", "fs-extra": "11.1.0", "globby": "11.1.0", - "mikro-orm": "~5.6.2", + "mikro-orm": "~5.6.7", "reflect-metadata": "0.1.13" }, "engines": { @@ -4138,9 +4153,9 @@ } }, "node_modules/@mikro-orm/mongodb": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.2.tgz", - "integrity": "sha512-z3dE0Bi2EgqCIZUnIzQyC1dlpBv3AmoUwvmmhvVmoz44avY0ZkZiXA/RFPocwvL9DZzKLI5zjMTj7dX9KPSyXw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.7.tgz", + "integrity": "sha512-aRrh9IKiEARKrG1RI87kc1rKWnQChMCkpte21YJhKPUjGlEhnGIkB/yY34edXfhxYvjQPmkhcDXgOe58oOk6WA==", "dependencies": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -5447,50 +5462,50 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@reldens/items-system": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.16.3.tgz", - "integrity": "sha512-8n/VcvdZjInPWRIsEqkGbDinrcrnFonVTzM87O/S9VfGfCWMzsuer76cUL5dCiOM6PNv5Fb5UVzWvFixv0S1CQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.17.0.tgz", + "integrity": "sha512-Emtish79IpdHFFTwj5HTliMi4kYEVr0iVo+ks15nHzE32p8/o9aERDVF7AGMotKFLA1FWdFfoqdGnbGf24+vPw==", "dependencies": { - "@reldens/modifiers": "^0.17.1", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.7" + "@reldens/modifiers": "^0.18.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0" } }, "node_modules/@reldens/modifiers": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.17.1.tgz", - "integrity": "sha512-zPEVGv5Q2C9RKJZcMC1afXMVadKJ97b47wELxXwlaDsD4AEuc1nQiJrHUZcC7maZtiOtfxHrbasO8u2/K3GMnA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.18.0.tgz", + "integrity": "sha512-j3vLXNRnRLDXildkuGPJUHWre4o5oe1d+novyPWW8bNVgUWJB5HXTorXvjoqT/OOkxfhgZ7xvlNT4Tw7pARRlA==", "dependencies": { - "@reldens/utils": "^0.17.7" + "@reldens/utils": "^0.18.0" } }, "node_modules/@reldens/skills": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.16.6.tgz", - "integrity": "sha512-UfqmZqRAodaWTq/Qzm6tuxHi81jeYlAfYElbtn/XDl/qCHO75O/enXDMy2hUndl3G+A1w8DLmm2ziFERm3zDrg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.17.0.tgz", + "integrity": "sha512-BkNT5PDQq3qzAlb9DUcgBXZCRcgIZ3hlsG1Zfx1sTiq/9t2XhrVVkPH2SzL7kNTdxuOgFlX6N/KNlV8LmDD4mg==", "dependencies": { - "@reldens/modifiers": "^0.17.1", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.8" + "@reldens/modifiers": "^0.18.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0" } }, "node_modules/@reldens/storage": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.12.2.tgz", - "integrity": "sha512-lpgCCnaEnmgud/4OS7vF+LRvDtcH8RgD6cbqZULNHFN6mGLS/fwzCEBAaQqIIC9xxHarwbNxTSgNFpM4S+mfMA==", - "dependencies": { - "@mikro-orm/core": "^5.6.1", - "@mikro-orm/mongodb": "^5.6.1", - "@reldens/utils": "^0.17.7", - "knex": "^2.3.0", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", + "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "dependencies": { + "@mikro-orm/core": "^5.6.7", + "@mikro-orm/mongodb": "^5.6.7", + "@reldens/utils": "^0.18.0", + "knex": "^2.4.1", "mysql": "^2.18.1", "objection": "^3.0.1" } }, "node_modules/@reldens/utils": { - "version": "0.17.8", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.17.8.tgz", - "integrity": "sha512-f6ssPTlFaji4tTWERxx+1Cz5zu13o23pmK75Xk0jxauk2kcq9JclwHcTPl8C3ojzQCHVhST1gBTeUOAVwLZ7OQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.0.tgz", + "integrity": "sha512-egOaCvMasNXLXlVNGA4Ngon0E9ngln7J9YIQZIHFhGGihgqdGppNewgAks+a/rQNR6x4E6109jpRcrlh7EHFUg==", "dependencies": { "await-event-emitter": "^2.0.2" } @@ -6486,9 +6501,9 @@ } }, "node_modules/bson": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", - "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "dependencies": { "buffer": "^5.6.0" }, @@ -7687,9 +7702,9 @@ } }, "node_modules/fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dependencies": { "reusify": "^1.0.4" } @@ -8976,9 +8991,9 @@ } }, "node_modules/knex": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz", - "integrity": "sha512-WMizPaq9wRMkfnwKXKXgBZeZFOSHGdtoSz5SaLAVNs3WRDfawt9O89T4XyH52PETxjV8/kRk0Yf+8WBEP/zbYw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", + "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", "dependencies": { "colorette": "2.0.19", "commander": "^9.1.0", @@ -9417,9 +9432,9 @@ } }, "node_modules/mikro-orm": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.2.tgz", - "integrity": "sha512-LzKIAtA/Zj8qO1yKNRJLYI+Vywm1nBQqb89vPM7abZBnqqarGB89CWTlAt6DfaG9bNk7v+FK0atO5dqMEfLT1Q==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.7.tgz", + "integrity": "sha512-mxkg+BDXmuQh4PYlWkv0Yysy33Yvg/31BZN4UWWZpV9gcLMYPQcaEF8qfSW7sMbrdNSJcvqf2MX6etmIARHJ9A==", "engines": { "node": ">= 14.0.0" } @@ -12539,9 +12554,9 @@ } }, "@aws-crypto/ie11-detection": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", - "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "optional": true, "requires": { "tslib": "^1.11.1" @@ -12556,16 +12571,16 @@ } }, "@aws-crypto/sha256-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", - "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "optional": true, "requires": { - "@aws-crypto/ie11-detection": "^2.0.0", - "@aws-crypto/sha256-js": "^2.0.0", - "@aws-crypto/supports-web-crypto": "^2.0.0", - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" @@ -12580,13 +12595,13 @@ } }, "@aws-crypto/sha256-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", - "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", "optional": true, "requires": { - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" }, "dependencies": { @@ -12599,9 +12614,9 @@ } }, "@aws-crypto/supports-web-crypto": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", - "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "optional": true, "requires": { "tslib": "^1.11.1" @@ -12616,12 +12631,12 @@ } }, "@aws-crypto/util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", - "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "optional": true, "requires": { - "@aws-sdk/types": "^3.110.0", + "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" }, @@ -12635,180 +12650,180 @@ } }, "@aws-sdk/abort-controller": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz", - "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.254.0.tgz", + "integrity": "sha512-ZBJFCCU7mIXGLk5GFXrSReyUR/kOBju0kzd7nVAAQQlfkmHZEuFhKFFMXkfJZG0SC0ezCbmR/EzIqJ2mTI+pRA==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/client-cognito-identity": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.238.0.tgz", - "integrity": "sha512-vjIVFzlkcUX9YT7pqg9NrvOTuqqsdYSesFgMLTCSWXBE5rPjWEK5ljIVOZtGn8MJQgwqS+Lqc8oB1vIfE+EaSA==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/client-sts": "3.238.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.254.0.tgz", + "integrity": "sha512-FZlTQqgY7v3A2SPq0wR+W1ApJ5hSB0qYezDfaDofpdZbHFKLKyQQr14n5PBrT57/I9Wo75rrgPLENhAqOu3TfQ==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.254.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" } }, "@aws-sdk/client-sso": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.238.0.tgz", - "integrity": "sha512-KHJJWP7hBDa9KLYiU5+hOb+3AAba93PhWebXkpKyQ/Bs+e7ECCreyLCwuME6uWTV01NDuFDpwZ6zUMpyNIcP6Q==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.254.0.tgz", + "integrity": "sha512-Ih80wJpGHa4nwxtTQvmSTT1UXQeHweYk+A6y779H1dtczr8h9Y2lXZa0C0TGcd1LEpL0nYHw0kJE3ZBw1/0nhw==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" } }, "@aws-sdk/client-sso-oidc": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.238.0.tgz", - "integrity": "sha512-kazcA2Kp+cXQRtaZi5/T5YFfU9J3nzu1tXJsh0xAm+J3S9LS1ertY1bSX6KBed2xuxx2mfum8JRqli0TJad/pA==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.254.0.tgz", + "integrity": "sha512-KRF/hBysJgrZpjRgg47Fa7YzC10Ypqf/FSeesJkN6l2lULosO+o0N+RD4t5LLWXrD10c9by6m1ueC6077z0fGQ==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "tslib": "^2.3.1" } }, "@aws-sdk/client-sts": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.238.0.tgz", - "integrity": "sha512-jQNwHqxWUGvWCN4o8KUFYQES8r41Oobu7x1KZOMrPhPxy27FUcDjBq/h85VoD2/AZlETSCZLiCnKV3KBh5pT5w==", - "optional": true, - "requires": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-sdk-sts": "3.226.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.254.0.tgz", + "integrity": "sha512-up+u5ik1pZ9Vo2MtcMCZ6pNAFhrePbH/IBFSgSsJlCU4AsGxPBsm59CE3/n/e84pFzhwVmFEUdavysIM+aP8LA==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/fetch-http-handler": "3.254.0", + "@aws-sdk/hash-node": "3.254.0", + "@aws-sdk/invalid-dependency": "3.254.0", + "@aws-sdk/middleware-content-length": "3.254.0", + "@aws-sdk/middleware-endpoint": "3.254.0", + "@aws-sdk/middleware-host-header": "3.254.0", + "@aws-sdk/middleware-logger": "3.254.0", + "@aws-sdk/middleware-recursion-detection": "3.254.0", + "@aws-sdk/middleware-retry": "3.254.0", + "@aws-sdk/middleware-sdk-sts": "3.254.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/middleware-user-agent": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/node-http-handler": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/smithy-client": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "@aws-sdk/util-body-length-browser": "3.188.0", "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-defaults-mode-browser": "3.254.0", + "@aws-sdk/util-defaults-mode-node": "3.254.0", + "@aws-sdk/util-endpoints": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", + "@aws-sdk/util-user-agent-browser": "3.254.0", + "@aws-sdk/util-user-agent-node": "3.254.0", "@aws-sdk/util-utf8-browser": "3.188.0", "@aws-sdk/util-utf8-node": "3.208.0", "fast-xml-parser": "4.0.11", @@ -12816,180 +12831,181 @@ } }, "@aws-sdk/config-resolver": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz", - "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.254.0.tgz", + "integrity": "sha512-+t5mi/SrZdAbSgg/5b/q3zVZsNQSyty2XX+znaRvBdANtIWIBdFLEMQp/L5NA+PSiW6VUXu9eXcsj0kJlAhTgQ==", "optional": true, "requires": { - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-cognito-identity": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.238.0.tgz", - "integrity": "sha512-FGVXGtGn7t/XacBdCKgqzxJNvorjCIar+F0oRsGq8aH09L/H8uGZ7qXvYvi9I94Qv7ay0Dy/KpGcMB53zt41UA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.254.0.tgz", + "integrity": "sha512-aZgGDn7lutcLcYp8z6z3PvfoGvrudv5mYj7mf/HDcEiHwpHGu+1t5qUw7VVD4e5yd3OCGJ2Rttohw9EIvXgcmg==", "optional": true, "requires": { - "@aws-sdk/client-cognito-identity": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-cognito-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-env": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz", - "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.254.0.tgz", + "integrity": "sha512-2CDwb7L1XGTY7Y8N3EsE1xqas0zNvrs4aOEv5XZNrKqE+9bvs8CiUwV4SB6VwSD+EPcOSm3QYEURUmj5EyLEZQ==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-imds": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz", - "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.254.0.tgz", + "integrity": "sha512-sM3N7FLz+svRGjTgwAybKBmu5tVfCJmd5HPEfKR0jfBWB1uq0u0J+65JiO/wfqn/ix+3ZyFfacSJDFjnSPu/KA==", "optional": true, "requires": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-ini": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.238.0.tgz", - "integrity": "sha512-WmPNtIYyUasjV7VQxvPNq7ihmx0vFsiKAtjNjjakdrt5TPoma4nUYb9tIG9SuG+kcp4DJIgRLJAgZtXbCcVimg==", - "optional": true, - "requires": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.254.0.tgz", + "integrity": "sha512-cqzrvuniurfiKmJsOlGyagUUwrZuOnEAxGDL0Olg5Ac3XByAO5AWH/mjy9P7u31j3vxybMz/Uw5kR7lV1q5sXQ==", + "optional": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-node": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.238.0.tgz", - "integrity": "sha512-/RN5EyGfgdIIJdFzv+O0nSaHX1/F3anQjTIBeVg8GJ+82m+bDxMdALsG+NzkYnLilN9Uhc1lSNjLBCoPa5DSEg==", - "optional": true, - "requires": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.238.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.254.0.tgz", + "integrity": "sha512-jjR/qLn0lwDJmeWwTMwWG2zk5GpjCdKts1Taxd5GFHHSzkH/FZG8Vr8u5yWRtoE/x876n+ItiRfcSrLTTwqkUQ==", + "optional": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-ini": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-process": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz", - "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.254.0.tgz", + "integrity": "sha512-vNm1AHMu5Lg1kOMk4ucWgaNO4zNAD7aeRssdBMnC7WqRT2xB8CUEWi+zJGNjbxzEeTLXQZuMa1VeRT3nPjYrzg==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-sso": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.238.0.tgz", - "integrity": "sha512-i70V4bFlCVYey3QARJ6XxKEg/4YuoFRnePV2oK37UHOGpEn49uXKwVZqLjzJgFHln7BPlC06cWDqrHUQIMvYrQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.254.0.tgz", + "integrity": "sha512-uFfAQ/sIWreDA79HpJqdL+LkVp/yGkdBrDhjbiXIyeVWy/NmQNKu8l0j+wGazk1r562TJbzZ0Gz6+wTsdUSPgA==", "optional": true, "requires": { - "@aws-sdk/client-sso": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/token-providers": "3.238.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-sso": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/token-providers": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz", - "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.254.0.tgz", + "integrity": "sha512-R/5qjAoCHEe7xmY5j0vges4xKpFpTgrwzdST822JVNWUobZmiDUqnn+1Xw4Qmomst625NOpgzsV4JuHsA4a8Ig==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/credential-providers": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.238.0.tgz", - "integrity": "sha512-wqj88z9UCbqxnd9XoaKrI+7oSN/13f1AP7cZWYKrzdvb3Ae5QmXmgMKXJdsQlWtPijchpPOQNGhd1rVogycgiA==", - "optional": true, - "requires": { - "@aws-sdk/client-cognito-identity": "3.238.0", - "@aws-sdk/client-sso": "3.238.0", - "@aws-sdk/client-sts": "3.238.0", - "@aws-sdk/credential-provider-cognito-identity": "3.238.0", - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.238.0", - "@aws-sdk/credential-provider-node": "3.238.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.238.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.254.0.tgz", + "integrity": "sha512-HoBfuSGU7cJ/My1gDLLePLlNjI/bAHQV+Ch8yqkipesfVuiO6aRaB7ipJYZS5OFJffkY9oJkZmGtqmrpfpziMQ==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.254.0", + "@aws-sdk/client-sso": "3.254.0", + "@aws-sdk/client-sts": "3.254.0", + "@aws-sdk/credential-provider-cognito-identity": "3.254.0", + "@aws-sdk/credential-provider-env": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/credential-provider-ini": "3.254.0", + "@aws-sdk/credential-provider-node": "3.254.0", + "@aws-sdk/credential-provider-process": "3.254.0", + "@aws-sdk/credential-provider-sso": "3.254.0", + "@aws-sdk/credential-provider-web-identity": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/fetch-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz", - "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.254.0.tgz", + "integrity": "sha512-/bbtNHe5JHFdKnCVr3Zx55sqs4c0F+7f1CC5cvTgH3O46wgIRM/6/rvE0YieXmfm3ho/GOhxBUzy59A0haKQGg==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/querystring-builder": "3.254.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-base64": "3.208.0", "tslib": "^2.3.1" } }, "@aws-sdk/hash-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz", - "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.254.0.tgz", + "integrity": "sha512-7FoB6BVbO+Z/NEOHeOAoUTyj8q+Pcdn4QpKvA4epRDrzMNcXy7MUNzzt148nkDssES09rgsN+KM8Zo2qgRYngg==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-buffer-from": "3.208.0", + "@aws-sdk/util-utf8": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/invalid-dependency": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz", - "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.254.0.tgz", + "integrity": "sha512-ueV0tXyGndCTZXnEv+AMeTfu+IqV2QzmGMXcakiwxDjg48H9X/bLnj+C96Sexond8jD8K0ub9HWhkBrvvAXlPA==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, @@ -13003,274 +13019,275 @@ } }, "@aws-sdk/middleware-content-length": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz", - "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.254.0.tgz", + "integrity": "sha512-IT7nDZA6WsaZSNp9M79xfkk/us4kGV4SIZ2R9gHT9MFqdmpmbr3EGhFLKXUHcAZfCcOdw+JNV/wHJiiN1JD/hg==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-endpoint": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz", - "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.254.0.tgz", + "integrity": "sha512-9fkDtSJdhEr91tWp4zLyKhHDGVyvUA0gDK+6wGYyorKCae2qX2TL+Fl6vsqY4PxrdTpXRBJDlJnEly9i48YKxg==", "optional": true, "requires": { - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/middleware-serde": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/url-parser": "3.254.0", "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-host-header": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz", - "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.254.0.tgz", + "integrity": "sha512-JG+OoyCMivnqTYiPZxRF+sgYEyQG68+PMl2843owvSxQQ25nH2Ih6DzLqH10c/uAN0PsiA8s/FfJBzhw9Xf0KA==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-logger": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz", - "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.254.0.tgz", + "integrity": "sha512-h3jEw58VUJkfqrwWMmp3Qc8293RFo4LMqxNAVsVwYEG6xb/RQ+JamsOx+t6aDsoOdKqhYngWwDGtgUZQ5wQQvg==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz", - "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.254.0.tgz", + "integrity": "sha512-/4tTvAXmIIMCs3giPIXN9aVJUGMoBMWw+9WS22u7nYNzwTe/k30DhS91uvwj7TLOOpFN0IBNXPCJ+T1OZn+ZXQ==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-retry": { - "version": "3.235.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz", - "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.254.0.tgz", + "integrity": "sha512-nHgris8NmtLzsH5iUA8geW6RAT1VRymjlieKFmM3CAYt2h2X8AtAiL/Wod+Pj3+jjRGk9YeGzOOGbzODHiRxnA==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/service-error-classification": "3.229.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/service-error-classification": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/util-middleware": "3.254.0", + "@aws-sdk/util-retry": "3.254.0", "tslib": "^2.3.1", "uuid": "^8.3.2" } }, "@aws-sdk/middleware-sdk-sts": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz", - "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.254.0.tgz", + "integrity": "sha512-Y074nmTp07thuOI6GePv8IKdL/OvkO1tn2l7QvnwQa3Sy/HyNai1V3MVtq4hRi1dgDjheKPVHPE+TnOmF3w5uA==", "optional": true, "requires": { - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/middleware-signing": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-serde": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz", - "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.254.0.tgz", + "integrity": "sha512-YuItb2nlKADTBItcn68eA8amX4quuR1+0GyFRkwssKS/iTjbIk+3gJ2s1zxkUhlyozH3U38Jvvqd+W9+gNpYIg==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-signing": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz", - "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.254.0.tgz", + "integrity": "sha512-HMVGf+yANjlKCUMFZJU2PNzbI9hbCgL+IX/Y4DGuQW9cp7EgZOxQre1LBKpcCqqPVQ4toIdfNH/K8uM2fpO6dg==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/signature-v4": "3.254.0", + "@aws-sdk/types": "3.254.0", + "@aws-sdk/util-middleware": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/middleware-stack": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz", - "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.254.0.tgz", + "integrity": "sha512-yPWRnjeLC0lPAEQbiqbC3+hnqXZ+uCSoSevGndU5KWMMiXLxKZn7Y0B3kG8NAnNNuPid+wYFWWU9rKiBRvWR/w==", "optional": true, "requires": { "tslib": "^2.3.1" } }, "@aws-sdk/middleware-user-agent": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz", - "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.254.0.tgz", + "integrity": "sha512-hp5UYRg3ysZXMFMv34nYexyom6Z3pdx+OmisJz4w3AMigT8y57Ps30Vg+1QYaGlQkI4vfvcmdZX2Q+kp+mb9gQ==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/node-config-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz", - "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.254.0.tgz", + "integrity": "sha512-3Bp3Gp2NOY9gab738xf07TysO5iB0Ib9qRNGDlxX8SX8fZDRnxrF2cn+Tjte42wrO54orwhSyuTaIlAqKeii8Q==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/node-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz", - "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.254.0.tgz", + "integrity": "sha512-DX2WJ3pub+3FF9GpoF5doERCn06MxS/UmmbKnIIokWQHjPZVomNh/1P3Cf9Jn9jeIPgh4UOg0uPD8cUm/cwHQw==", "optional": true, "requires": { - "@aws-sdk/abort-controller": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/abort-controller": "3.254.0", + "@aws-sdk/protocol-http": "3.254.0", + "@aws-sdk/querystring-builder": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/property-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz", - "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.254.0.tgz", + "integrity": "sha512-BLZF/LDFjAgv2ZY0vhThU58k++Aw+SK7qNU7XT0D84q5iWlYRKptQEvSSvIkBSI/rZoppOFhK7W80I8kNNbh+Q==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/protocol-http": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz", - "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.254.0.tgz", + "integrity": "sha512-4o/I/qhMUTp70njwWe3ttyRJSAKegnr8l3oVWAf1/q1ZHpcxbRRZEDvrkx4KSunFeXTTGHcff1oyLSRG/cKMsQ==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/querystring-builder": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz", - "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.254.0.tgz", + "integrity": "sha512-Er+pOGTrPxelrzggibduO+eB1ClaU2BhjA8gd0nORS3kqktQggG3tKmRSIilegi9WOa3awCk6CnnuAf0pBrbUA==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-uri-escape": "3.201.0", "tslib": "^2.3.1" } }, "@aws-sdk/querystring-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz", - "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.254.0.tgz", + "integrity": "sha512-WwRD99dwGo2aIrRjLHUAXaWCZ+3fj88IhIwciWTqrHBS3TQWXllOOQmYo7f+aMBB4Q1K6KdKITNi8L7aUuDv2g==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/service-error-classification": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz", - "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.254.0.tgz", + "integrity": "sha512-8GHqMJBBF9yoMBG/Nf9PusUSMFjG8ygps/cSJPlgcG2vbFn8BCdBZVc4ptXqICZUnBB/6lrxy8nCmNUaru48jg==", "optional": true }, "@aws-sdk/shared-ini-file-loader": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz", - "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.254.0.tgz", + "integrity": "sha512-UH4YTXuG+q004vA+jNrVhrD5XQCIAgpL/eriObJnQpKUVef1mkkEDHZs8+8+ZPsk4p/iBrIJ3lXNf7iDA/BFzw==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/signature-v4": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz", - "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.254.0.tgz", + "integrity": "sha512-9FoEnipA9hAgEp6oqIT3+hobF+JgIXIn5QV8kAB7QGxEDqs/pdpEbGc9qbxi0ghdjvqzOSDir9gNI3w0cL8Aug==", "optional": true, "requires": { "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-middleware": "3.254.0", "@aws-sdk/util-uri-escape": "3.201.0", + "@aws-sdk/util-utf8": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/smithy-client": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz", - "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.254.0.tgz", + "integrity": "sha512-SI0jz9JfWi1IaakDX/26xliKTIMJpzwwDoyQPEfZ/L0KKdpr2gNhljA3sR2pZ2EM1oqOaXpMHAunSzv7EBpBWg==", "optional": true, "requires": { - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/middleware-stack": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/token-providers": { - "version": "3.238.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.238.0.tgz", - "integrity": "sha512-vYUwmy0kTzA99mJCVvad+/5RDlWve/xxnppT8DJK3JIdAgskp+rULY+joVnq2NSl489UAioUnFGs57vUxi8Pog==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.254.0.tgz", + "integrity": "sha512-i3W+YWrMtgdFPDWW/m56xrkBhqrB6beKgQi46oSM/aFZ3ZAkFJLfbsLK99LWzVxtzELTSFjJWY54r+Au/hb2kQ==", "optional": true, "requires": { - "@aws-sdk/client-sso-oidc": "3.238.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/client-sso-oidc": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/shared-ini-file-loader": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/types": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz", - "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.254.0.tgz", + "integrity": "sha512-xDEDk6ZAGFO0URPgB6R2mvQANYlojHLjLC9zzOzl07F+uqYS30yZDIg4UFcqPt/x48v7mxlKZpbaZgYI2ZLgGA==", "optional": true, "requires": { "tslib": "^2.3.1" } }, "@aws-sdk/url-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz", - "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.254.0.tgz", + "integrity": "sha512-Za0JGUa9p5GQ8t2tVtKaRSjLUxrmEdnBlUiZ2zKm86wFxgQnjbMwzD3mvyJ5OaVsXScU5vzc3CXHIXSvS7h7Ng==", "optional": true, "requires": { - "@aws-sdk/querystring-parser": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/querystring-parser": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, @@ -13322,38 +13339,38 @@ } }, "@aws-sdk/util-defaults-mode-browser": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz", - "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.254.0.tgz", + "integrity": "sha512-vj/s+BuqNKTHN9bsZ/HY7vpBWbo3F+4c3/ZoKSZa5Jc7jAuGCbx3zWwHdJFDgvbqLvsTBw80Q9d/CDy9pKj/tQ==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "bowser": "^2.11.0", "tslib": "^2.3.1" } }, "@aws-sdk/util-defaults-mode-node": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz", - "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.254.0.tgz", + "integrity": "sha512-gvD2+Uf60c2BgUYv2d6R4dSpO/CbvybqblgF8lKZCsHkDWzfEdPv9nlJgUWM1cuMKQ0hBZ3cL3ilOwVKRVPyiQ==", "optional": true, "requires": { - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/config-resolver": "3.254.0", + "@aws-sdk/credential-provider-imds": "3.254.0", + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/property-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, "@aws-sdk/util-endpoints": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.226.0.tgz", - "integrity": "sha512-iqOkac/zLmyPBUJd7SLN0PeZMkOmlGgD5PHmmekTClOkce2eUjK9SNX1PzL73aXPoPTyhg9QGLH8uEZEQ8YUzg==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.254.0.tgz", + "integrity": "sha512-BzBIOnhVrs4RFTpGZErZfAV1VhqWglxn047VYijmCQe8Aejq4mJAaepSwHYar++XC0+pduD5YO8IidW8z/1vQQ==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "tslib": "^2.3.1" } }, @@ -13376,21 +13393,21 @@ } }, "@aws-sdk/util-middleware": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz", - "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.254.0.tgz", + "integrity": "sha512-gn7vInNTRBo2QatOB+uU99JwV53wf/zlTUnUK0qOuebtSDLMdiO+msiMi2ctz9vMIrtc2XMXNQro1aE0aUPy4w==", "optional": true, "requires": { "tslib": "^2.3.1" } }, "@aws-sdk/util-retry": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz", - "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.254.0.tgz", + "integrity": "sha512-IVA4wAOJpVssEIbJmeq1fdDYvrkOqYFK9Pz4tERmMz33003fyY92dU468Lulw8MnsSALYiwWUoWSFg9L5RCTug==", "optional": true, "requires": { - "@aws-sdk/service-error-classification": "3.229.0", + "@aws-sdk/service-error-classification": "3.254.0", "tslib": "^2.3.1" } }, @@ -13404,24 +13421,34 @@ } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz", - "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.254.0.tgz", + "integrity": "sha512-2HvwH8l7ln4qTDsU3rgH9NvSSo5qhX+2Lenb6XvNnIMkL4r/tPhNIaGKtoQRfpzLH378Mm9XEQnJM5UXFRWuTA==", "optional": true, "requires": { - "@aws-sdk/types": "3.226.0", + "@aws-sdk/types": "3.254.0", "bowser": "^2.11.0", "tslib": "^2.3.1" } }, "@aws-sdk/util-user-agent-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz", - "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==", + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.254.0.tgz", + "integrity": "sha512-6nc9bmRP+2JqbBJ5oRZZRU8l35X3VcWF5j8XvmamWjIABsanc6Gv6NV4qAa3imPjIyWNiShZn/YkTBYs1exsdg==", + "optional": true, + "requires": { + "@aws-sdk/node-config-provider": "3.254.0", + "@aws-sdk/types": "3.254.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-utf8": { + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", + "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", "optional": true, "requires": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-buffer-from": "3.208.0", "tslib": "^2.3.1" } }, @@ -15556,23 +15583,23 @@ } }, "@mikro-orm/core": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.2.tgz", - "integrity": "sha512-FDKJBL1gjT9d36E9dyC5S9OxoeEQzy7lIj/SpijU8BDDC6gpn27HJ00DLqG+gLdepXBYdDdf3B/ELi+OGxZKCw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.7.tgz", + "integrity": "sha512-XLCY3S10nz7uUFGTFypUuaO48cQAhCUiSsQBxpqqhlOqVz6yxGYdqAe7SmMKuplmpOoVL8OGDZrULzd0bIjtYA==", "requires": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", "fs-extra": "11.1.0", "globby": "11.1.0", - "mikro-orm": "~5.6.2", + "mikro-orm": "~5.6.7", "reflect-metadata": "0.1.13" } }, "@mikro-orm/mongodb": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.2.tgz", - "integrity": "sha512-z3dE0Bi2EgqCIZUnIzQyC1dlpBv3AmoUwvmmhvVmoz44avY0ZkZiXA/RFPocwvL9DZzKLI5zjMTj7dX9KPSyXw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.7.tgz", + "integrity": "sha512-aRrh9IKiEARKrG1RI87kc1rKWnQChMCkpte21YJhKPUjGlEhnGIkB/yY34edXfhxYvjQPmkhcDXgOe58oOk6WA==", "requires": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -16371,50 +16398,50 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@reldens/items-system": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.16.3.tgz", - "integrity": "sha512-8n/VcvdZjInPWRIsEqkGbDinrcrnFonVTzM87O/S9VfGfCWMzsuer76cUL5dCiOM6PNv5Fb5UVzWvFixv0S1CQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.17.0.tgz", + "integrity": "sha512-Emtish79IpdHFFTwj5HTliMi4kYEVr0iVo+ks15nHzE32p8/o9aERDVF7AGMotKFLA1FWdFfoqdGnbGf24+vPw==", "requires": { - "@reldens/modifiers": "^0.17.1", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.7" + "@reldens/modifiers": "^0.18.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0" } }, "@reldens/modifiers": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.17.1.tgz", - "integrity": "sha512-zPEVGv5Q2C9RKJZcMC1afXMVadKJ97b47wELxXwlaDsD4AEuc1nQiJrHUZcC7maZtiOtfxHrbasO8u2/K3GMnA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.18.0.tgz", + "integrity": "sha512-j3vLXNRnRLDXildkuGPJUHWre4o5oe1d+novyPWW8bNVgUWJB5HXTorXvjoqT/OOkxfhgZ7xvlNT4Tw7pARRlA==", "requires": { - "@reldens/utils": "^0.17.7" + "@reldens/utils": "^0.18.0" } }, "@reldens/skills": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.16.6.tgz", - "integrity": "sha512-UfqmZqRAodaWTq/Qzm6tuxHi81jeYlAfYElbtn/XDl/qCHO75O/enXDMy2hUndl3G+A1w8DLmm2ziFERm3zDrg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.17.0.tgz", + "integrity": "sha512-BkNT5PDQq3qzAlb9DUcgBXZCRcgIZ3hlsG1Zfx1sTiq/9t2XhrVVkPH2SzL7kNTdxuOgFlX6N/KNlV8LmDD4mg==", "requires": { - "@reldens/modifiers": "^0.17.1", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.8" + "@reldens/modifiers": "^0.18.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0" } }, "@reldens/storage": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.12.2.tgz", - "integrity": "sha512-lpgCCnaEnmgud/4OS7vF+LRvDtcH8RgD6cbqZULNHFN6mGLS/fwzCEBAaQqIIC9xxHarwbNxTSgNFpM4S+mfMA==", - "requires": { - "@mikro-orm/core": "^5.6.1", - "@mikro-orm/mongodb": "^5.6.1", - "@reldens/utils": "^0.17.7", - "knex": "^2.3.0", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", + "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "requires": { + "@mikro-orm/core": "^5.6.7", + "@mikro-orm/mongodb": "^5.6.7", + "@reldens/utils": "^0.18.0", + "knex": "^2.4.1", "mysql": "^2.18.1", "objection": "^3.0.1" } }, "@reldens/utils": { - "version": "0.17.8", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.17.8.tgz", - "integrity": "sha512-f6ssPTlFaji4tTWERxx+1Cz5zu13o23pmK75Xk0jxauk2kcq9JclwHcTPl8C3ojzQCHVhST1gBTeUOAVwLZ7OQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.0.tgz", + "integrity": "sha512-egOaCvMasNXLXlVNGA4Ngon0E9ngln7J9YIQZIHFhGGihgqdGppNewgAks+a/rQNR6x4E6109jpRcrlh7EHFUg==", "requires": { "await-event-emitter": "^2.0.2" } @@ -17239,9 +17266,9 @@ } }, "bson": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", - "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "requires": { "buffer": "^5.6.0" }, @@ -18148,9 +18175,9 @@ } }, "fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "requires": { "reusify": "^1.0.4" } @@ -19092,9 +19119,9 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knex": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz", - "integrity": "sha512-WMizPaq9wRMkfnwKXKXgBZeZFOSHGdtoSz5SaLAVNs3WRDfawt9O89T4XyH52PETxjV8/kRk0Yf+8WBEP/zbYw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", + "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", "requires": { "colorette": "2.0.19", "commander": "^9.1.0", @@ -19353,9 +19380,9 @@ } }, "mikro-orm": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.2.tgz", - "integrity": "sha512-LzKIAtA/Zj8qO1yKNRJLYI+Vywm1nBQqb89vPM7abZBnqqarGB89CWTlAt6DfaG9bNk7v+FK0atO5dqMEfLT1Q==" + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.7.tgz", + "integrity": "sha512-mxkg+BDXmuQh4PYlWkv0Yysy33Yvg/31BZN4UWWZpV9gcLMYPQcaEF8qfSW7sMbrdNSJcvqf2MX6etmIARHJ9A==" }, "mime": { "version": "3.0.0", diff --git a/package.json b/package.json index 6f125c2cc..bb02e12b7 100644 --- a/package.json +++ b/package.json @@ -78,11 +78,11 @@ "@parcel/resolver-default": "2.8.0", "@parcel/reporter-dev-server": "2.8.0", "@parcel/core": "2.8.0", - "@reldens/items-system": "^0.16.3", - "@reldens/modifiers": "^0.17.1", - "@reldens/skills": "^0.16.6", - "@reldens/storage": "^0.12.2", - "@reldens/utils": "^0.17.8", + "@reldens/items-system": "^0.17.0", + "@reldens/modifiers": "^0.18.0", + "@reldens/skills": "^0.17.0", + "@reldens/storage": "^0.13.0", + "@reldens/utils": "^0.18.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", diff --git a/theme/default/assets/features/teams/templates/team-accept.html b/theme/default/assets/features/teams/templates/team-accept.html new file mode 100644 index 000000000..fa86ebfa2 --- /dev/null +++ b/theme/default/assets/features/teams/templates/team-accept.html @@ -0,0 +1,4 @@ +
+ Accept team invite from player {{playerName}}: + +
diff --git a/theme/default/assets/features/teams/templates/team-invite.html b/theme/default/assets/features/teams/templates/team-invite.html new file mode 100644 index 000000000..f991a0c55 --- /dev/null +++ b/theme/default/assets/features/teams/templates/team-invite.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/theme/default/assets/features/teams/templates/ui-clan.html b/theme/default/assets/features/teams/templates/ui-clan.html new file mode 100644 index 000000000..0356c838b --- /dev/null +++ b/theme/default/assets/features/teams/templates/ui-clan.html @@ -0,0 +1,5 @@ +
+ close +
+
+open diff --git a/theme/default/assets/features/teams/templates/ui-team.html b/theme/default/assets/features/teams/templates/ui-team.html new file mode 100644 index 000000000..186f2fd9d --- /dev/null +++ b/theme/default/assets/features/teams/templates/ui-team.html @@ -0,0 +1,5 @@ +
+ close +
+
+open From fd9be064fe5ff0ba293d11145aa2b5faff581df9 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 24 Jan 2023 21:15:14 +0100 Subject: [PATCH 05/82] - Reldens - v4.0.0 - Teams and clans message actions. --- lib/inventory/server/message-actions.js | 4 +- lib/rooms/server/login.js | 2 +- lib/teams/constants.js | 8 ++- lib/teams/server/message-actions.js | 39 ++++++++++++ lib/teams/server/message-actions/team-join.js | 37 +++++++++++ .../server/message-actions/try-team-start.js | 33 ++++++++++ lib/teams/server/plugin.js | 13 ++-- .../server/subscribers/player-subscriber.js | 47 ++++++++++++++ lib/teams/server/team-updates-handler.js | 51 ++++++++++++++++ lib/teams/server/team.js | 61 +++++++++++++++++++ 10 files changed, 284 insertions(+), 11 deletions(-) create mode 100644 lib/teams/server/message-actions.js create mode 100644 lib/teams/server/message-actions/team-join.js create mode 100644 lib/teams/server/message-actions/try-team-start.js create mode 100644 lib/teams/server/subscribers/player-subscriber.js create mode 100644 lib/teams/server/team-updates-handler.js create mode 100644 lib/teams/server/team.js diff --git a/lib/inventory/server/message-actions.js b/lib/inventory/server/message-actions.js index 7b48ffa4e..a35f8f70b 100644 --- a/lib/inventory/server/message-actions.js +++ b/lib/inventory/server/message-actions.js @@ -1,8 +1,6 @@ /** * - * Reldens - MessageActions - * - * Server side messages actions. + * Reldens - InventoryMessageActions * */ diff --git a/lib/rooms/server/login.js b/lib/rooms/server/login.js index 3ba661b22..1af39b0a2 100644 --- a/lib/rooms/server/login.js +++ b/lib/rooms/server/login.js @@ -45,7 +45,7 @@ class RoomLogin extends Room } let loginResult = await this.loginManager.processUserRequest(options); if(sc.hasOwn(loginResult, 'error')){ - // @TODO - Beta - Improve login errors, use a send message with type and here just return false. + // @TODO - Beta - Improve login errors, use send message with type and here just return false. ErrorManager.error(loginResult.error); } if(sc.hasOwn(options, 'selectedPlayer')){ diff --git a/lib/teams/constants.js b/lib/teams/constants.js index bbda47550..853d999df 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -4,8 +4,14 @@ * */ +let pref = 'tm.' + module.exports.TeamsConst = { + TEAM_PREF: pref, ACTIONS: { - TEAM_INVITE: 't.inv' + TEAM_INVITE: pref+'inv', + TEAM_ACCEPTED: pref+'acp', + TEAM_LEAVE: pref+'lev', + TEAM_UPDATE: pref+'upd' } }; diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/message-actions.js new file mode 100644 index 000000000..86a5881b2 --- /dev/null +++ b/lib/teams/server/message-actions.js @@ -0,0 +1,39 @@ +/** + * + * Reldens - TeamsMessageActions + * + */ + +const { TryTeamStart } = require('./message-actions/try-team-start'); +const { TeamJoin } = require('./message-actions/team-join'); +const { TeamsConst } = require('../constants'); +const { sc } = require('@reldens/utils'); + +class TeamsMessageActions +{ + + async executeMessageActions(client, data, room, playerSchema) + { + if(!sc.hasOwn(data, 'act')){ + return false; + } + if(0 !== data.act.indexOf(TeamsConst.TEAM_PREF)){ + return false; + } + if(TeamsConst.ACTIONS.TEAM_INVITE === data.act){ + TryTeamStart.execute(client, data, room, playerSchema); + return true; + } + if(TeamsConst.ACTIONS.TEAM_ACCEPTED === data.act && '1' === data.value){ + TeamJoin.execute(client, data, room, playerSchema); + return true; + } + if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ + playerSchema?.currentTeam.leave(); + return true; + } + } + +} + +module.exports.TeamsMessageActions = TeamsMessageActions; diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js new file mode 100644 index 000000000..17c4f2d32 --- /dev/null +++ b/lib/teams/server/message-actions/team-join.js @@ -0,0 +1,37 @@ +/** + * + * Reldens - TeamsMessageActions + * + */ + +const { Team } = require('../team'); +const { TeamUpdatesHandler } = require('../team-updates-handler'); +const { Logger, sc } = require('@reldens/utils'); + +class TeamJoin +{ + + static execute(client, data, room, playerSchema) + { + if(playerSchema.sessionId === data.id){ + Logger.info('The player is trying to join a team with himself.', playerSchema.sessionId, data); + return false; + } + if(playerSchema.currentTeam){ + playerSchema.currentTeam.leave(); + } + let teamOwnerPlayer = sc.get(room.activePlayers, data.id, false); + if(false === teamOwnerPlayer){ + Logger.error('Player team owner not found.', teamOwnerPlayer, data); + return false; + } + playerSchema.currentTeam = teamOwnerPlayer.currentTeam || new Team({ + owner: teamOwnerPlayer, + sharedProperties: room.config.get('client/ui/teams/sharedProperties') + }); + TeamUpdatesHandler.updateTeamPlayers(playerSchema.currentTeam); + } + +} + +module.exports.TeamJoin = TeamJoin; diff --git a/lib/teams/server/message-actions/try-team-start.js b/lib/teams/server/message-actions/try-team-start.js new file mode 100644 index 000000000..cc00b9722 --- /dev/null +++ b/lib/teams/server/message-actions/try-team-start.js @@ -0,0 +1,33 @@ +/** + * + * Reldens - TryTeamStart + * + */ + +const { TeamsConst } = require('../../constants'); +const { Logger, sc } = require('@reldens/utils'); + +class TryTeamStart +{ + static execute(client, data, room, playerSchema) + { + if(playerSchema.sessionId === data.id){ + Logger.info('The player is trying to team up with himself.', playerSchema.sessionId, data); + return false; + } + let toPlayerClient = sc.get(room.activePlayers, data.id, false); + if(false === toPlayerClient){ + Logger.error('Player client not found.', toPlayerClient, data); + return false; + } + let sendData = { + act: TeamsConst.ACTIONS.TEAM_INVITE, + listener: 'team', + from: playerSchema.playerName, + id: playerSchema.sessionId, + }; + toPlayerClient.client.send('*', sendData); + } +} + +module.exports.TryTeamStart = TryTeamStart; \ No newline at end of file diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index c076cbfec..d643c6f84 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -5,6 +5,8 @@ */ const { PluginInterface } = require('../../features/plugin-interface'); +const { TeamsMessageActions } = require('./message-actions'); +const { PlayerSubscriber } = require('subscribers/player-subscriber'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface @@ -16,13 +18,12 @@ class TeamsPlugin extends PluginInterface if(!this.events){ Logger.error('EventsManager undefined in TeamsPlugin.'); } - this.events.on('reldens.beforeSuperInitialGameData', async (superInitialGameData, roomGame) => { - await this.onBeforeSuperInitialGameData(superInitialGameData, roomGame); + this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { + roomMessageActions.teams = new TeamsMessageActions(); + }); + this.events.on('reldens.createPlayerAfter', async (client, authResult, currentPlayer, room) => { + await PlayerSubscriber.enrichPlayerWithClan(client, currentPlayer, room, this.events, this.modelsManager); }); - } - - async onBeforeSuperInitialGameData(superInitialGameData, roomGame) - { } } diff --git a/lib/teams/server/subscribers/player-subscriber.js b/lib/teams/server/subscribers/player-subscriber.js new file mode 100644 index 000000000..08f258ea8 --- /dev/null +++ b/lib/teams/server/subscribers/player-subscriber.js @@ -0,0 +1,47 @@ +/** + * + * Reldens - PlayerSubscriber + * + */ + +class PlayerSubscriber +{ + + static async enrichPlayerWithClan(client, currentPlayer, room, events, modelsManager) + { + /* + let serverProps = { + owner: currentPlayer, + client: new ClientWrapper({client, room}), + persistence: true, + ownerIdProperty: 'player_id', + eventsManager: events, + modelsManager: modelsManager, + itemClasses: room.config.getWithoutLogs('server/customClasses/inventory/items', {}), + groupClasses: room.config.getWithoutLogs('server/customClasses/inventory/groups', {}), + itemsModelData: room.config.inventory.items + }; + // @TODO - BETA - Add new event here for the server properties. + let inventoryServer = new ItemsServer(serverProps); + // broadcast player sessionId to share animations: + inventoryServer.client.sendTargetProps.broadcast.push('sessionId'); + // load all the items here and then create instances for later use: + await inventoryServer.dataServer.loadOwnerItems(); + // create player inventory: + currentPlayer.inventory = inventoryServer; + // @NOTE: here we send the groups data to generate the player interface instead of set them in the current + // player inventory because for this specific implementation we don't need recursive groups lists in the + // server for each player. + let sendData = { + act: ItemsConst.ACTION_SET_GROUPS, + owner: currentPlayer.inventory.manager.getOwnerId(), + groups: room.config.get('inventory/groups/groupBaseData') + }; + // @TODO - BETA - Add new event here for the sendData. + client.send('*', sendData); + */ + } + +} + +module.exports.PlayerSubscriber = PlayerSubscriber; diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js new file mode 100644 index 000000000..9f5dbe86d --- /dev/null +++ b/lib/teams/server/team-updates-handler.js @@ -0,0 +1,51 @@ +/** + * + * Reldens - TeamsMessageActions + * + */ + +const { TeamsConst } = require('../constants'); +const { sc } = require('@reldens/utils'); + +class TeamUpdatesHandler +{ + + static updateTeamPlayers(team) + { + let sendData = {act: TeamsConst.ACTIONS.TEAM_UPDATE}; + sendData.players = this.fetchPlayersData(team); + this.teamBroadcast(team, sendData); + } + + static fetchPlayersData(team) + { + let teamPlayersId = Object.keys(team.players); + let playersData = {}; + for(let i of teamPlayersId){ + playersData[i] = this.fetchPlayerProperties(team.players[i], team.sharedProperties); + } + return playersData; + } + + static fetchPlayerProperties(player, sharedProperties) + { + let playerProperties = {}; + for(let propertyName of sharedProperties){ + let propertyPath = propertyName.split('/'); + playerProperties[propertyPath[propertyPath.length]] = sc.get(propertyPath, player); + } + return playerProperties; + } + + static teamBroadcast(team, sendData) + { + for(let i of Object.keys(team.players)){ + let player = team.players[i]; + player.client.send('*', sendData); + } + } + + +} + +module.exports.TeamUpdatesHandler = TeamUpdatesHandler; \ No newline at end of file diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js new file mode 100644 index 000000000..c6207cb90 --- /dev/null +++ b/lib/teams/server/team.js @@ -0,0 +1,61 @@ +/** + * + * Reldens - Team + * + */ + +const { ErrorManager, sc } = require('@reldens/utils'); + +class Team +{ + constructor(props) + { + this.level = sc.get(props, 'level', 1); + this.players = sc.get(props, 'players', {}); + this.modifiers = sc.get(props, 'modifiers', {}); + this.sharedProperties = sc.get(props, 'sharedProperties', {}); + this.owner = sc.get(props, 'owner', false); + if(false === this.owner){ + ErrorManager.error('Team owner undefined.', props); + } + } + + join(player) + { + this.players[player.player_id] = player; + this.applyModifiers(player); + } + + leave(player) + { + this.revertModifiers(player); + delete this.players[player.player_id]; + } + + applyModifiers(player) + { + let modifiersKeys = Object.keys(this.modifiers); + if(0 === modifiersKeys.length){ + return false; + } + for(let i of modifiersKeys){ + let modifier = this.modifiers[i]; + modifier.apply(player); + } + } + + revertModifiers(player) + { + let modifiersKeys = Object.keys(this.modifiers); + if(0 === modifiersKeys.length){ + return false; + } + for(let i of modifiersKeys){ + let modifier = this.modifiers[i]; + modifier.revert(player); + } + } + +} + +module.exports.Team = Team; \ No newline at end of file From e1837262b72a5d6a8120b4469abcfe44930046f6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 25 Jan 2023 13:04:40 +0100 Subject: [PATCH 06/82] - Reldens - v4.0.0 - Subscriber fix. --- lib/teams/server/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index d643c6f84..9e8da5d6a 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -6,7 +6,7 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamsMessageActions } = require('./message-actions'); -const { PlayerSubscriber } = require('subscribers/player-subscriber'); +const { PlayerSubscriber } = require('./subscribers/player-subscriber'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface From 7e28f8a2571d64f7b46eda478fd99946f08638a2 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 25 Jan 2023 16:58:53 +0100 Subject: [PATCH 07/82] - Reldens - v4.0.0 - Update migration script. --- migrations/production/beta.26-sql-update.sql | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index b048053b1..d99e4b120 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -68,6 +68,50 @@ CREATE TABLE `clan_members` ( INDEX `FK__players` (`player_id`) USING BTREE ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; +# Inventory tables fix: +ALTER TABLE `items_inventory` DROP FOREIGN KEY `FK_items_inventory_items_item`; +ALTER TABLE `items_item` DROP FOREIGN KEY `FK_items_item_items_group`; +ALTER TABLE `items_item_modifiers` DROP FOREIGN KEY `FK_items_item_modifiers_items_item`; +ALTER TABLE `objects_items_inventory` DROP FOREIGN KEY `objects_items_inventory_ibfk_1`; + +ALTER TABLE `items_group` + CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST; +ALTER TABLE `items_inventory` + CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + CHANGE COLUMN `owner_id` `owner_id` INT(10) UNSIGNED NOT NULL AFTER `id`, + CHANGE COLUMN `item_id` `item_id` INT(10) UNSIGNED NOT NULL AFTER `owner_id`; +ALTER TABLE `items_item` + CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + CHANGE COLUMN `group_id` `group_id` INT(10) UNSIGNED NULL DEFAULT NULL AFTER `type`; +ALTER TABLE `items_item_modifiers` + CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + CHANGE COLUMN `item_id` `item_id` INT(10) UNSIGNED NOT NULL AFTER `id`; +ALTER TABLE `objects_items_inventory` + CHANGE COLUMN `item_id` `item_id` INT(10) UNSIGNED NOT NULL AFTER `owner_id`; +ALTER TABLE `features` + CHANGE COLUMN `is_enabled` `is_enabled` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `title`; + +ALTER TABLE `items_inventory` ADD CONSTRAINT `FK_items_inventory_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; +ALTER TABLE `items_item` ADD CONSTRAINT `FK_items_item_items_group` FOREIGN KEY (`group_id`) REFERENCES `items_group` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; +ALTER TABLE `items_item_modifiers` ADD CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; +ALTER TABLE `objects_items_inventory` ADD CONSTRAINT `FK_objects_items_inventory_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; + +# Rewards: +CREATE TABLE `rewards` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `object_id` INT(10) UNSIGNED NOT NULL, + `item_id` INT(10) UNSIGNED NOT NULL, + `drop_rate` INT(10) UNSIGNED NOT NULL, + `drop_quantity` INT(10) UNSIGNED NOT NULL, + `is_unique` TINYINT(3) UNSIGNED NOT NULL, + `was_given` TINYINT(3) UNSIGNED NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `FK_rewards_items_item` (`item_id`) USING BTREE, + INDEX `FK_rewards_objects` (`object_id`) USING BTREE, + CONSTRAINT `FK_rewards_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION, + CONSTRAINT `FK_rewards_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + ####################################################################################################################### SET FOREIGN_KEY_CHECKS = 1; From def72cb111ed4f520ab141c3e6ad245bc67744c5 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 27 Jan 2023 21:11:32 +0100 Subject: [PATCH 08/82] - Reldens - v4.0.0 - Team join and creation. --- lib/game/client/room-events.js | 5 +- lib/game/constants.js | 4 + lib/inventory/client/plugin.js | 4 +- lib/inventory/client/trade-message-handler.js | 24 +- .../client/trade-message-listener.js | 6 +- lib/inventory/constants.js | 2 +- lib/inventory/server/message-actions.js | 2 +- lib/inventory/server/plugin.js | 2 +- lib/objects/server/object/type/npc-object.js | 2 +- lib/rooms/server/login.js | 2 +- lib/rooms/server/scene.js | 2 +- lib/teams/client/plugin.js | 34 +-- lib/teams/client/team-create-target-action.js | 5 +- lib/teams/client/team-message-handler.js | 259 ++++++++++++++++++ lib/teams/client/team-message-listener.js | 31 +++ lib/teams/constants.js | 10 + lib/teams/server/message-actions.js | 10 +- lib/teams/server/message-actions/team-join.js | 21 +- .../server/message-actions/try-team-start.js | 10 +- lib/teams/server/plugin.js | 4 +- lib/teams/server/team-updates-handler.js | 14 +- lib/teams/server/team.js | 33 ++- lib/users/server/plugin.js | 5 +- migrations/production/beta.26-sql-update.sql | 5 +- .../features/teams/templates/team-accept.html | 4 +- .../teams/templates/team-container.html | 9 + .../features/teams/templates/team-invite.html | 2 +- .../features/teams/templates/ui-team.html | 5 - .../features/teams/templates/ui-teams.html | 5 + 29 files changed, 429 insertions(+), 92 deletions(-) create mode 100644 lib/teams/client/team-message-handler.js create mode 100644 lib/teams/client/team-message-listener.js create mode 100644 theme/default/assets/features/teams/templates/team-container.html delete mode 100644 theme/default/assets/features/teams/templates/ui-team.html create mode 100644 theme/default/assets/features/teams/templates/ui-teams.html diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 4729d58a0..4236ce2b3 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -2,9 +2,6 @@ * * Reldens - RoomEvents * - * This class will listen the scene-rooms and run the related actions, it will also register the other modules action - * into the room events. - * */ const { PlayerEngine } = require('../../users/client/player-engine'); @@ -26,8 +23,10 @@ class RoomEvents this.gameEngine = gameManager.gameEngine; this.roomName = roomName; this.events = gameManager.events; + // @TODO - BETA - Move the following inside a single property called "metadata". this.objectsUi = {}; this.tradeUi = {}; + this.teamUi = {}; } async activateRoom(room, previousScene = false) diff --git a/lib/game/constants.js b/lib/game/constants.js index 87f23739c..e7bd327c4 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -55,5 +55,9 @@ module.exports.GameConst = { CSS_FILE: 'styles.css', ADMIN_SCSS_FILE: 'reldens-admin.scss', ADMIN_CSS_FILE: 'reldens-admin.css' + }, + LABELS: { + YES: 'Yes', + NO: 'No' } }; diff --git a/lib/inventory/client/plugin.js b/lib/inventory/client/plugin.js index 39b3c2722..32363d3d7 100644 --- a/lib/inventory/client/plugin.js +++ b/lib/inventory/client/plugin.js @@ -20,10 +20,10 @@ class InventoryPlugin extends PluginInterface setup(props) { - // @TODO - Refactor plugin, extract all the methods into new classes. + // @TODO - BETA - Refactor plugin, extract all the methods into new classes. this.gameManager = sc.get(props, 'gameManager', false); this.tradeTargetAction = new TradeTargetAction(); - // @TODO - Make the dialogBox template load on it's own so we can use the same object from cache everytime. + // @TODO - BETA - Make the dialogBox template load on it's own so we can use the same object from cache everytime. // @NOTE: the tradeUi works as preload for the trade template which at the end is an dialog-box. this.tradeUi = new UserInterface(this.gameManager, {id: 'trade', type: 'trade'}); if(!this.gameManager){ diff --git a/lib/inventory/client/trade-message-handler.js b/lib/inventory/client/trade-message-handler.js index a53b31100..49d943425 100644 --- a/lib/inventory/client/trade-message-handler.js +++ b/lib/inventory/client/trade-message-handler.js @@ -62,7 +62,7 @@ class TradeMessageHandler id: tradeUiKey, title: 'Trade request from:', content: this.message.from, - options: {'1':{'label':'Accept','value':1},'2':{'label':'Decline','value':2}}, + options: this.gameManager.config.get('client/ui/options/acceptOrDecline'), overrideSendOptions: { act: InventoryConst.ACTIONS.TRADE_ACCEPTED, id: this.message.id @@ -318,13 +318,13 @@ class TradeMessageHandler let functionLabels = ObjectsConst.TRADE_ACTIONS_FUNCTION_NAME; let templateParams = { tradeActionKey: this.message.id, - confirmLabel: this.gameManager.config.get('trade/titles/confirmLabel', functionLabels.CONFIRM), - disconfirmLabel: this.gameManager.config.get('trade/titles/disconfirmLabel', functionLabels.DISCONFIRM), - cancelLabel: this.gameManager.config.get('trade/titles/cancelLabel', functionLabels.CANCEL), + confirmLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/confirmLabel', functionLabels.CONFIRM), + disconfirmLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/disconfirmLabel', functionLabels.DISCONFIRM), + cancelLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/cancelLabel', functionLabels.CANCEL), myItems: tradeItems, - myItemsTitle: this.gameManager.config.get('trade/titles/myItems', 'My Items:'), - pushedToTradeTitle: this.gameManager.config.get('trade/titles/pushedToTradeTitle', 'Sending:'), - gotFromTradeTitle: this.gameManager.config.get('trade/titles/gotFromTradeTitle', 'Receiving:'), + myItemsTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/myItems', 'My Items:'), + pushedToTradeTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/pushedToTradeTitle', 'Sending:'), + gotFromTradeTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/gotFromTradeTitle', 'Receiving:'), playerConfirmedLabel: this.playerConfirmedLabel(), }; return this.gameManager.gameEngine.parseTemplate(messageTemplate, templateParams); @@ -332,9 +332,11 @@ class TradeMessageHandler playerConfirmedLabel() { - return this.message.playerConfirmed - ? this.message.with + this.gameManager.config.get('trade/titles/playerConfirmedLabel', ' CONFIRMED') - : ''; + if(!this.message.playerConfirmed){ + return ''; + } + // @TODO - BETA - Change all the fixed concatenated names by .replace('%name', nameVariable). + return this.message.with + this.gameManager.config.getWithoutLogs('client/trade/titles/playerConfirmedLabel', ' CONFIRMED'); } createTradeItemBox(item, exchangeDataItem) @@ -428,4 +430,4 @@ class TradeMessageHandler } -module.exports.TradeMessageHandler = TradeMessageHandler; \ No newline at end of file +module.exports.TradeMessageHandler = TradeMessageHandler; diff --git a/lib/inventory/client/trade-message-listener.js b/lib/inventory/client/trade-message-listener.js index f9b14796f..30a958514 100644 --- a/lib/inventory/client/trade-message-listener.js +++ b/lib/inventory/client/trade-message-listener.js @@ -14,12 +14,12 @@ class TradeMessageListener { let message = sc.get(props, 'message', false); if(!message){ - Logger.error('Missing message data.', props); + Logger.error('Missing message data on TradeMessageListener.', props); return false; } let roomEvents = sc.get(props, 'roomEvents', false); if(!roomEvents){ - Logger.error('Missing RoomEvents.', props); + Logger.error('Missing RoomEvents on TradeMessageListener.', props); return false; } let tradeMessageHandler = new TradeMessageHandler({roomEvents, message}); @@ -28,4 +28,4 @@ class TradeMessageListener } -module.exports.TradeMessageListener = TradeMessageListener; \ No newline at end of file +module.exports.TradeMessageListener = TradeMessageListener; diff --git a/lib/inventory/constants.js b/lib/inventory/constants.js index 33674ff99..7a00e7a0c 100644 --- a/lib/inventory/constants.js +++ b/lib/inventory/constants.js @@ -18,7 +18,7 @@ module.exports.InventoryConst = { EQUIPMENT_CLOSE: 'equipment-close', EQUIPMENT_OPEN: 'equipment-open', INVENTORY_PREF: pref, - // TODO - BETA - Move inside ACTIONS. + // @TODO - BETA - Move inside ACTIONS. ACTION_REMOVE: pref+'Rm', ACTION_USE: pref+'Use', ACTION_EQUIP: pref+'Eqi', diff --git a/lib/inventory/server/message-actions.js b/lib/inventory/server/message-actions.js index a35f8f70b..bbee4597e 100644 --- a/lib/inventory/server/message-actions.js +++ b/lib/inventory/server/message-actions.js @@ -195,7 +195,7 @@ class InventoryMessageActions { // @TODO - BETA - Refactor when include false conditions in the shortcuts and include a new property "tradable". let fromInventoryItems = [ - // TODO - BETA - Refactor to make findItemsByPropertyValue always return an array. + // @TODO - BETA - Refactor to make findItemsByPropertyValue always return an array. ...(playerOwner.inventory.manager.findItemsByPropertyValue('equipped', false) || []), ...(playerOwner.inventory.manager.findItemsByPropertyValue('equipped', undefined) || []) ]; diff --git a/lib/inventory/server/plugin.js b/lib/inventory/server/plugin.js index 5c119cc49..744220f28 100644 --- a/lib/inventory/server/plugin.js +++ b/lib/inventory/server/plugin.js @@ -26,7 +26,7 @@ class InventoryPlugin extends PluginInterface this.events.on('reldens.serverReady', async (event) => { await ServerSubscriber.initializeInventory(event.serverManager.configManager, this.modelsManager); }); - this.events.on('reldens.createPlayerAfter', async (client, authResult, currentPlayer, room) => { + this.events.on('reldens.createPlayerStatsAfter', async (client, authResult, currentPlayer, room) => { await PlayerSubscriber.createPlayerInventory(client, currentPlayer, room, this.events, this.modelsManager); }); // when the client sent a message to any room it will be checked by all the global messages defined: diff --git a/lib/objects/server/object/type/npc-object.js b/lib/objects/server/object/type/npc-object.js index 0bd86c7d9..bbd1854c4 100644 --- a/lib/objects/server/object/type/npc-object.js +++ b/lib/objects/server/object/type/npc-object.js @@ -27,7 +27,7 @@ class NpcObject extends AnimationObject // @NOTE: interaction area is how far the player can be from the object to validate the actions on click, this // area will be the valid-margin surrounding the object. this.interactionArea = this.config.get('server/objects/actions/interactionsDistance'); - this.closeInteractionOnOutOfReach = this.config.get( + this.closeInteractionOnOutOfReach = this.config.getWithoutLogs( 'server/objects/actions/closeInteractionOnOutOfReach', true ); diff --git a/lib/rooms/server/login.js b/lib/rooms/server/login.js index 1af39b0a2..105afc812 100644 --- a/lib/rooms/server/login.js +++ b/lib/rooms/server/login.js @@ -45,7 +45,7 @@ class RoomLogin extends Room } let loginResult = await this.loginManager.processUserRequest(options); if(sc.hasOwn(loginResult, 'error')){ - // @TODO - Beta - Improve login errors, use send message with type and here just return false. + // @TODO - BETA - Improve login errors, use send message with type and here just return false. ErrorManager.error(loginResult.error); } if(sc.hasOwn(options, 'selectedPlayer')){ diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index 0d028ca26..b3768bb8f 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -98,6 +98,7 @@ class RoomScene extends RoomLogin playerName: authResult.player.name, role_id: authResult.role_id, playerData: authResult.player, + // loggedUserFound, client: client }; await this.events.emit('reldens.joinRoomEnd', this, client, options, authResult, loggedUserFound); @@ -157,7 +158,6 @@ class RoomScene extends RoomLogin currentPlayer.playStartTime = Date.now(); this.state.addPlayerToState(currentPlayer, client.sessionId); // @TODO - BETA - Create player body using a new pack in the world package. - // create body for server physics and assign the body to the player: currentPlayer.physicalBody = this.roomWorld.createPlayerBody({ id: client.sessionId, width: this.config.get('client/players/physicalBody/width'), diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index eaf00669e..4163fd58e 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -7,6 +7,8 @@ const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetActions } = require('./team-create-target-action'); +const { TeamMessageListener } = require('./team-message-listener'); +const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface @@ -17,13 +19,13 @@ class TeamsPlugin extends PluginInterface this.teamTargetActions = new TeamTargetActions(); this.gameManager = sc.get(props, 'gameManager', false); // @NOTE: the tradeUi works as preload for the trade template which at the end is a dialog-box. - this.teamsUi = new UserInterface(this.gameManager, {id: 'trade', type: 'trade'}); + this.teamsUi = new UserInterface(this.gameManager, {id: TeamsConst.KEY, type: TeamsConst.KEY}); if(!this.gameManager){ - Logger.error('Game Manager undefined in InventoryPlugin.'); + Logger.error('Game Manager undefined in TeamsPlugin.'); } this.events = sc.get(props, 'events', false); if(!this.events){ - Logger.error('EventsManager undefined in InventoryPlugin.'); + Logger.error('EventsManager undefined in TeamsPlugin.'); } this.events.on('reldens.preloadUiScene', (preloadScene) => { this.preloadTemplates(preloadScene); @@ -38,38 +40,24 @@ class TeamsPlugin extends PluginInterface this.events.on('reldens.createUiScene', (preloadScene) => { return this.onPreloadUiScene(preloadScene); }); - this.gameManager.config.client.message.listeners['trade'] = new TradeMessageListener(); */ + this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } preloadTemplates(preloadScene) { let teamsTemplatePath = 'assets/features/teams/templates/'; + preloadScene.load.html(TeamsConst.KEY, teamsTemplatePath+'teams.html'); + preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'clan.html'); preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); } onPreloadUiScene(preloadScene) { - /* - this.uiManager = new InventoryUi(preloadScene); - this.uiManager.createUi(); - let inventoryPanel = preloadScene.getUiElement('inventory').getChildByProperty( - 'id', - InventoryConst.INVENTORY_ITEMS - ); - let equipmentPanel = preloadScene.getUiElement('equipment').getChildByProperty( - 'id', - InventoryConst.EQUIPMENT_ITEMS - ); - if(!inventoryPanel || !equipmentPanel){ - Logger.error(['Inventory/Equipment UI not found.', inventoryPanel, equipmentPanel]); - return false; - } - let manager = preloadScene.gameManager.inventory.manager; - // listen for inventory events: - this.listenInventoryEvents(preloadScene, inventoryPanel, equipmentPanel); - */ + // this.uiManager = new TeamsUi(preloadScene); + // this.uiManager.createUi(); + this.listenInventoryEvents(preloadScene); } onPlayerAdd(key, roomEvents, player) diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 5ea2da0a9..5c89853a6 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -26,13 +26,14 @@ class TeamTargetActions uiTarget.getChildByID('target-container').innerHTML += gameManager.gameEngine.parseTemplate( teamPlayerActionsTemplate, { + // @TODO - BETA - Create translations table with a loader and processor. playerName: targetName, - playerId: target.id + playerId: target.id, + inviteLabel: gameManager.config.get('team/titles/inviteLabel', TeamsConst.LABELS.INVITE_BUTTON_LABEL) } ); gameManager.gameDom.getElement('.team-invite-'+target.id+' button')?.addEventListener('click', () => { let sendData = {act: TeamsConst.ACTIONS.TEAM_INVITE, id: target.id}; - console.log({sendData}); gameManager.room.send('*', sendData); }); } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js new file mode 100644 index 000000000..991bc9b1d --- /dev/null +++ b/lib/teams/client/team-message-handler.js @@ -0,0 +1,259 @@ +/** + * + * Reldens - TeamMessageHandler + * + */ + +const { ErrorManager, Logger, sc } = require('@reldens/utils'); +const { TeamsConst } = require('../constants'); +const { ObjectsConst } = require('../../objects/constants'); +const { UserInterface } = require('../../game/client/user-interface'); + +class TeamMessageHandler +{ + + constructor(props) + { + this.roomEvents = sc.get(props, 'roomEvents', false); + this.message = sc.get(props, 'message', false); + this.gameManager = this.roomEvents?.gameManager; + this.gameDom = this.gameManager?.gameDom; + this.uiScene = this.gameManager?.gameEngine?.uiScene; + this.validate(); + } + + validate() + { + if(!this.roomEvents){ + ErrorManager.error('Missing RoomEvents.'); + } + if(!this.message){ + ErrorManager.error('Missing message.'); + } + if(!this.gameManager){ + ErrorManager.error('Missing GameManager.'); + } + if(!this.uiScene){ + ErrorManager.error('Missing UiScene.'); + } + } + + updateContents() + { + if(TeamsConst.ACTIONS.TEAM_INVITE === this.message.act){ + return this.showTeamRequest(); + } + if( + TeamsConst.ACTIONS.TEAM_ACCEPTED === this.message.act + || TeamsConst.ACTIONS.TEAM_UPDATE === this.message.act + ){ + return this.showTeamBox(); + } + } + + showTeamRequest() + { + let teamUiKey = 'team'+this.message.id; + let uiDoesNotExists = this.createTeamUi(teamUiKey); + /* + let acceptLabel = gameManager.config.getWithoutLogs( + 'client/team/titles/acceptFromPlayerLabel', + TeamsConst.LABELS.ACCEPT_FROM_PLAYER + ); + acceptFromPlayerLabel: acceptLabel.replace('%playerName', targetName), + yesLabel: gameManager.config.getWithoutLogs('client/team/titles/yesLabel', GameConst.LABELS.YES), + noLabel: gameManager.config.getWithoutLogs('client/team/titles/noLabel', GameConst.LABELS.NO), + */ + this.roomEvents.initUi({ + id: teamUiKey, + title: this.gameManager.config.getWithoutLogs( + 'client/teams/labels/requestFromTitle', + TeamsConst.LABELS.TEAM_REQUEST_FROM + ), + content: this.message.from, + options: this.gameManager.config.getWithoutLogs('client/ui/options/acceptOrDecline'), + overrideSendOptions: { + act: TeamsConst.ACTIONS.TEAM_ACCEPTED, + id: this.message.id + } + }); + if(uiDoesNotExists){ + this.gameDom.getElement('#opt-2-'+teamUiKey)?.addEventListener('click', () => { + // send the close click action to the server: + this.gameDom.getElement('#box-close-'+teamUiKey)?.click(); + }); + } + } + + showTeamBox() + { + let teamUiKey = TeamsConst.KEY+this.message.id; + this.createTeamUi(teamUiKey); + let title = this.gameManager.config.getWithoutLogs( + 'client/team/labels/leaderNameTitle', + TeamsConst.LABELS.LEADER_NAME_TITLE + ).replace('%leaderName', this.message.with); + this.roomEvents.initUi({ + id: teamUiKey, + title, + content: '', + options: {} + }); + let container = this.gameManager.gameDom.getElement('#box-'+teamUiKey+' .box-content'); + if(!container){ + Logger.error('Missing container: "#box-'+teamUiKey+' .box-content".'); + return false; + } + let players = sc.get(this.message, 'players', false); + this.updateTeamBox(players, container); + } + + createTeamUi(teamUiKey) + { + let uiDoesNotExists = !sc.hasOwn(this.roomEvents.teamUi, teamUiKey); + if(uiDoesNotExists){ + this.roomEvents.teamUi[teamUiKey] = new UserInterface( + this.gameManager, + {id: teamUiKey, type: TeamsConst.KEY}, + 'assets/html/dialog-box.html', + TeamsConst.KEY + ); + this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); + } + return uiDoesNotExists; + } + + updateTeamBox(players, container) + { + if(!this.message.data){ + return; + } + let teamMembers = ''; + for(let i of Object.keys(players)){ + teamMembers += this.createTeamMemberBox(players[i]); + } + container.innerHTML = this.createTeamContainer(teamMembers); + this.activateTeamTargetActions(players); + this.activateTeamLeaveButtonAction(); + } + + createTeamContainer(teamMembers) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let messageTemplate = this.uiScene.cache.html.get('teamContainer'); + if(!messageTemplate){ + Logger.error('Missing template "teamContainer".'); + return ''; + } + let templateParams = { + teamActionKey: this.message.id, + disbandLabel: this.gameManager.config.getWithoutLogs( + 'client/team/titles/disbandLabel', + TeamsConst.LABELS.DISBAND + ), + teamMembers + }; + return this.gameManager.gameEngine.parseTemplate(messageTemplate, templateParams); + } + + activateTeamLeaveButtonAction() + { + let confirmButton = this.gameManager.gameDom.getElement('.confirm-'+this.message.id); + confirmButton?.addEventListener('click', () => { + this.gameManager.activeRoomEvents.room.send('*', { + act: TeamsConst.ACTIONS.TEAM_ACCEPTED, + id: this.message.id, + value: this.message.id + }); + }); + let disconfirmButton = this.gameManager.gameDom.getElement('.disconfirm-'+this.message.id); + disconfirmButton?.addEventListener('click', () => { + this.gameManager.activeRoomEvents.room.send('*', { + act: TeamsConst.ACTIONS.TEAM_DECLINED, + id: this.message.id, + value: this.message.id + }); + }); + let cancelButton = this.gameManager.gameDom.getElement('.cancel-'+this.message.id); + cancelButton?.addEventListener('click', () => { + this.gameDom.getElement('#box-close-'+'team'+this.message.id)?.click(); + }); + } + + createTeamMemberBox(playerData) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let messageTemplate = this.uiScene.cache.html.get('teamPlayerData'); + if(!messageTemplate){ + Logger.error('Missing template "teamPlayerData".'); + return ''; + } + return this.gameManager.gameEngine.parseTemplate(messageTemplate, { + id: playerData.player_id, + name: playerData.name, + teamAction: this.createTeamActionContent(playerData) + }); + } + + createTeamActionContent(item, teamAction) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let messageTemplate = this.uiScene.cache.html.get('inventoryTeamAction'); + if(!messageTemplate){ + Logger.error('Missing template "inventoryTeamAction".'); + return ''; + } + return this.gameManager.gameEngine.parseTemplate(messageTemplate, { + key: item.key, + id: item.getInventoryId(), + teamAction: teamAction || sc.get(item, 'teamAction', '') + }); + } + + createTeamActionRemove(item) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let messageTemplate = this.uiScene.cache.html.get('inventoryTeamActionRemove'); + if(!messageTemplate){ + Logger.error('Missing template "inventoryTeamActionRemove".'); + return ''; + } + return this.gameManager.gameEngine.parseTemplate(messageTemplate, { + key: item.key, + id: item.uid, + teamAction: 'remove' + }); + } + + activateTeamTargetActions(items) + { + for(let i of Object.keys(items)){ + let item = items[i]; + let itemContainerSelector = '.team-item-to-be-'+item.teamAction+'.team-item-'+item.uid + +' .team-action-'+item.teamAction; + let itemButtonSelector = itemContainerSelector+' button'; + let itemActionButton = this.gameDom.getElement(itemButtonSelector); + if(!itemActionButton){ + Logger.error('Activate team item "'+item.uid+'" action button not found.'); + continue; + } + itemActionButton.addEventListener('click', () => { + let qtySelector = this.gameDom.getElement('.team-item-'+item.getInventoryId()+' .item-qty input'); + let qtySelected = qtySelector?.value || 1; + let dataSend = { + act: TeamsConst.ACTIONS.TEAM_ACTION, + id: this.message.id, + value: item.teamAction, + itemId: item.getInventoryId(), + itemKey: item.key, + qty: Number(qtySelected) + }; + dataSend[ObjectsConst.TEAM_ACTIONS.SUB_ACTION] = ObjectsConst.TEAM_ACTIONS.ADD; + this.gameManager.activeRoomEvents.room.send('*', dataSend); + }); + } + } + +} + +module.exports.TeamMessageHandler = TeamMessageHandler; \ No newline at end of file diff --git a/lib/teams/client/team-message-listener.js b/lib/teams/client/team-message-listener.js new file mode 100644 index 000000000..b565f0237 --- /dev/null +++ b/lib/teams/client/team-message-listener.js @@ -0,0 +1,31 @@ +/** + * + * Reldens - TeamMessageListener + * + */ + +const { TeamMessageHandler } = require('./team-message-handler'); +const { Logger, sc } = require('@reldens/utils'); + +class TeamMessageListener +{ + + async executeClientMessageActions(props) + { + let message = sc.get(props, 'message', false); + if(!message){ + Logger.error('Missing message data on TeamMessageListener.', props); + return false; + } + let roomEvents = sc.get(props, 'roomEvents', false); + if(!roomEvents){ + Logger.error('Missing RoomEvents on TeamMessageListener.', props); + return false; + } + let tradeMessageHandler = new TeamMessageHandler({roomEvents, message}); + tradeMessageHandler.updateContents(); + } + +} + +module.exports.TeamMessageListener = TeamMessageListener; diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 853d999df..a615c85a2 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -7,11 +7,21 @@ let pref = 'tm.' module.exports.TeamsConst = { + KEY: 'teams', + CLAN_KEY: 'clan', TEAM_PREF: pref, ACTIONS: { TEAM_INVITE: pref+'inv', TEAM_ACCEPTED: pref+'acp', + TEAM_DECLINED: pref+'dec', TEAM_LEAVE: pref+'lev', TEAM_UPDATE: pref+'upd' + }, + LABELS: { + INVITE_BUTTON_LABEL: 'Team - Invite', + TEAM_REQUEST_FROM: 'Team request from:', + LEADER_NAME_TITLE: 'Team leader: %leaderName', + DISBAND: 'Disband Team', + ACCEPT_FROM_PLAYER: 'Accept team invite from player %playerName:' } }; diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/message-actions.js index 86a5881b2..370b308fd 100644 --- a/lib/teams/server/message-actions.js +++ b/lib/teams/server/message-actions.js @@ -12,6 +12,11 @@ const { sc } = require('@reldens/utils'); class TeamsMessageActions { + constructor(props) + { + this.plugin = props.plugin; + } + async executeMessageActions(client, data, room, playerSchema) { if(!sc.hasOwn(data, 'act')){ @@ -25,11 +30,12 @@ class TeamsMessageActions return true; } if(TeamsConst.ACTIONS.TEAM_ACCEPTED === data.act && '1' === data.value){ - TeamJoin.execute(client, data, room, playerSchema); + TeamJoin.execute(client, data, room, playerSchema, this.plugin); return true; } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - playerSchema?.currentTeam.leave(); + this.plugin.teams[data.id].leave(playerSchema); + delete this.plugin.playersTeamsRelation[playerSchema.id]; return true; } } diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index 17c4f2d32..9a40d6223 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -11,25 +11,34 @@ const { Logger, sc } = require('@reldens/utils'); class TeamJoin { - static execute(client, data, room, playerSchema) + static execute(client, data, room, playerSchema, teamsPlugin) { if(playerSchema.sessionId === data.id){ Logger.info('The player is trying to join a team with himself.', playerSchema.sessionId, data); return false; } if(playerSchema.currentTeam){ - playerSchema.currentTeam.leave(); + teamsPlugin.teams[playerSchema.currentTeam].leave(playerSchema); } - let teamOwnerPlayer = sc.get(room.activePlayers, data.id, false); - if(false === teamOwnerPlayer){ + let teamOwnerPlayer = room.state.players.get(data.id); + if(!teamOwnerPlayer){ Logger.error('Player team owner not found.', teamOwnerPlayer, data); return false; } - playerSchema.currentTeam = teamOwnerPlayer.currentTeam || new Team({ + let teamOwnerClient = room.activePlayers[data.id]; + if(!teamOwnerClient){ + Logger.error('Player team owner client not found.', teamOwnerClient, data); + return false; + } + let currentTeam = teamsPlugin.teams[data.id] || new Team({ owner: teamOwnerPlayer, + ownerClient: teamOwnerClient.client, sharedProperties: room.config.get('client/ui/teams/sharedProperties') }); - TeamUpdatesHandler.updateTeamPlayers(playerSchema.currentTeam); + currentTeam.join(playerSchema, client); + playerSchema.currentTeam = data.id; + teamsPlugin.playersTeamsRelation[playerSchema.id] = data.id; + TeamUpdatesHandler.updateTeamPlayers(currentTeam); } } diff --git a/lib/teams/server/message-actions/try-team-start.js b/lib/teams/server/message-actions/try-team-start.js index cc00b9722..5766d1d3d 100644 --- a/lib/teams/server/message-actions/try-team-start.js +++ b/lib/teams/server/message-actions/try-team-start.js @@ -15,18 +15,18 @@ class TryTeamStart Logger.info('The player is trying to team up with himself.', playerSchema.sessionId, data); return false; } - let toPlayerClient = sc.get(room.activePlayers, data.id, false); - if(false === toPlayerClient){ - Logger.error('Player client not found.', toPlayerClient, data); + let toPlayer = sc.get(room.activePlayers, data.id, false); + if(false === toPlayer){ + Logger.error('Player not found.', toPlayer, data); return false; } let sendData = { act: TeamsConst.ACTIONS.TEAM_INVITE, - listener: 'team', + listener: TeamsConst.KEY, from: playerSchema.playerName, id: playerSchema.sessionId, }; - toPlayerClient.client.send('*', sendData); + toPlayer.client.send('*', sendData); } } diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 9e8da5d6a..e3f2a2959 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -19,11 +19,13 @@ class TeamsPlugin extends PluginInterface Logger.error('EventsManager undefined in TeamsPlugin.'); } this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { - roomMessageActions.teams = new TeamsMessageActions(); + roomMessageActions.teams = new TeamsMessageActions({plugin: this}); }); this.events.on('reldens.createPlayerAfter', async (client, authResult, currentPlayer, room) => { await PlayerSubscriber.enrichPlayerWithClan(client, currentPlayer, room, this.events, this.modelsManager); }); + this.teams = {}; + this.playersTeamsRelation = {}; } } diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 9f5dbe86d..1a25be56a 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -14,6 +14,8 @@ class TeamUpdatesHandler { let sendData = {act: TeamsConst.ACTIONS.TEAM_UPDATE}; sendData.players = this.fetchPlayersData(team); + // @TODO - HERE! + // console.log(sendData); this.teamBroadcast(team, sendData); } @@ -27,21 +29,23 @@ class TeamUpdatesHandler return playersData; } - static fetchPlayerProperties(player, sharedProperties) + static fetchPlayerProperties(playerSchema, sharedProperties) { let playerProperties = {}; for(let propertyName of sharedProperties){ let propertyPath = propertyName.split('/'); - playerProperties[propertyPath[propertyPath.length]] = sc.get(propertyPath, player); + // @TODO - HERE! + // playerProperties[propertyPath[propertyPath.length]] = sc.get(propertyPath, playerSchema); } return playerProperties; } static teamBroadcast(team, sendData) { - for(let i of Object.keys(team.players)){ - let player = team.players[i]; - player.client.send('*', sendData); + for(let i of Object.keys(team.clients)){ + // @TODO - HERE! + // @TODO - BETA - Clone and remove current player from sendData. + team.clients[i].send('*', sendData); } } diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index c6207cb90..fd1c2e06d 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -11,28 +11,39 @@ class Team constructor(props) { this.level = sc.get(props, 'level', 1); - this.players = sc.get(props, 'players', {}); this.modifiers = sc.get(props, 'modifiers', {}); this.sharedProperties = sc.get(props, 'sharedProperties', {}); this.owner = sc.get(props, 'owner', false); if(false === this.owner){ ErrorManager.error('Team owner undefined.', props); } + this.ownerClient = sc.get(props, 'ownerClient', false); + if(false === this.owner){ + ErrorManager.error('Team owner client undefined.', props); + } + let players = {}; + let clients = {}; + players[this.owner.id] = this.owner; + clients[this.owner.id] = this.ownerClient; + this.players = sc.get(props, 'players', players); + this.clients = sc.get(props, 'clients', clients); } - join(player) + join(playerSchema, client) { - this.players[player.player_id] = player; - this.applyModifiers(player); + this.players[playerSchema.id] = playerSchema; + this.clients[playerSchema.id] = client; + this.applyModifiers(playerSchema); } - leave(player) + leave(playerSchema) { - this.revertModifiers(player); - delete this.players[player.player_id]; + this.revertModifiers(playerSchema); + delete this.clients[playerSchema.id]; + delete this.players[playerSchema.id]; } - applyModifiers(player) + applyModifiers(playerSchema) { let modifiersKeys = Object.keys(this.modifiers); if(0 === modifiersKeys.length){ @@ -40,11 +51,11 @@ class Team } for(let i of modifiersKeys){ let modifier = this.modifiers[i]; - modifier.apply(player); + modifier.apply(playerSchema); } } - revertModifiers(player) + revertModifiers(playerSchema) { let modifiersKeys = Object.keys(this.modifiers); if(0 === modifiersKeys.length){ @@ -52,7 +63,7 @@ class Team } for(let i of modifiersKeys){ let modifier = this.modifiers[i]; - modifier.revert(player); + modifier.revert(playerSchema); } } diff --git a/lib/users/server/plugin.js b/lib/users/server/plugin.js index 92d8e38cf..fd3a2b1a5 100644 --- a/lib/users/server/plugin.js +++ b/lib/users/server/plugin.js @@ -54,7 +54,7 @@ class UsersPlugin extends PluginInterface if(!this.lifeProp){ this.lifeProp = configProcessor.get('client/actions/skills/affectedProperty'); } - this.events.on('reldens.createPlayerAfter', async (client, authResult, currentPlayer, roomScene) => { + this.events.on('reldens.createPlayerStatsAfter', async (client, authResult, currentPlayer, roomScene) => { await this.updatePlayersLifebar(roomScene, client, currentPlayer); await this.updateEnemiesLifebar(roomScene); }); @@ -173,11 +173,12 @@ class UsersPlugin extends PluginInterface configProcessor.client.players.initialStats = this.statsByKey; } - async onCreatePlayerAfterAppendStats(client, authResult, currentPlayer) + async onCreatePlayerAfterAppendStats(client, authResult, currentPlayer, roomScene) { let {stats, statsBase} = await this.processStatsData('playerStats', currentPlayer.player_id); currentPlayer.stats = stats; currentPlayer.statsBase = statsBase; + this.events.emit('reldens.createPlayerStatsAfter', client, authResult, currentPlayer, roomScene); } async processStatsData(model, playerId) diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index d99e4b120..4778a65c1 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -5,7 +5,6 @@ SET FOREIGN_KEY_CHECKS = 0; ####################################################################################################################### # Config Types: - CREATE TABLE `config_types` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `label` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci', @@ -31,6 +30,9 @@ UPDATE `config` SET `type` = @boolean_id WHERE `type` = 'b'; UPDATE `config` SET `type` = @float_id WHERE `type` = 'i'; UPDATE `config` SET `type` = @json_id WHERE `type` = 'j'; UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; +INSERT INTO `config` VALUES (NULL, 'client', 'ui/options/acceptOrDecline', '{"1":{"label":"Accept","value":1},"2":{"label":"Decline","value":2}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/leaderNameTitle', 'Team leader: %leaderName', @string_id); ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; @@ -47,7 +49,6 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', 'stats INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); # Clan and members: - CREATE TABLE `clan` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `owner_id` INT(10) UNSIGNED NOT NULL, diff --git a/theme/default/assets/features/teams/templates/team-accept.html b/theme/default/assets/features/teams/templates/team-accept.html index fa86ebfa2..a3163807b 100644 --- a/theme/default/assets/features/teams/templates/team-accept.html +++ b/theme/default/assets/features/teams/templates/team-accept.html @@ -1,4 +1,4 @@
- Accept team invite from player {{playerName}}: - + {{acceptTeamFromPlayerLabel}} +
diff --git a/theme/default/assets/features/teams/templates/team-container.html b/theme/default/assets/features/teams/templates/team-container.html new file mode 100644 index 000000000..3f2dbc93f --- /dev/null +++ b/theme/default/assets/features/teams/templates/team-container.html @@ -0,0 +1,9 @@ +
+
+
+
+
+ +
+
+
diff --git a/theme/default/assets/features/teams/templates/team-invite.html b/theme/default/assets/features/teams/templates/team-invite.html index f991a0c55..af582e411 100644 --- a/theme/default/assets/features/teams/templates/team-invite.html +++ b/theme/default/assets/features/teams/templates/team-invite.html @@ -1,3 +1,3 @@
- +
diff --git a/theme/default/assets/features/teams/templates/ui-team.html b/theme/default/assets/features/teams/templates/ui-team.html deleted file mode 100644 index 186f2fd9d..000000000 --- a/theme/default/assets/features/teams/templates/ui-team.html +++ /dev/null @@ -1,5 +0,0 @@ -
- close -
-
-open diff --git a/theme/default/assets/features/teams/templates/ui-teams.html b/theme/default/assets/features/teams/templates/ui-teams.html new file mode 100644 index 000000000..cfa22562c --- /dev/null +++ b/theme/default/assets/features/teams/templates/ui-teams.html @@ -0,0 +1,5 @@ +
+ close +
+
+open From 7eaf94c9d9a4da53607ec49ec75f1f4866f99091 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 29 Jan 2023 20:10:02 +0100 Subject: [PATCH 09/82] - Reldens - v4.0.0 - Team data broadcast. --- lib/teams/server/team-updates-handler.js | 41 +++++++++++--------- migrations/production/beta.26-sql-update.sql | 2 +- package-lock.json | 14 +++---- package.json | 2 +- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 1a25be56a..bf24b461a 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -12,11 +12,19 @@ class TeamUpdatesHandler static updateTeamPlayers(team) { - let sendData = {act: TeamsConst.ACTIONS.TEAM_UPDATE}; - sendData.players = this.fetchPlayersData(team); - // @TODO - HERE! - // console.log(sendData); - this.teamBroadcast(team, sendData); + let clientsKeys = Object.keys(team.clients); + if(0 === clientsKeys.length){ + return false; + } + let playersList = this.fetchPlayersData(team); + if(0 === Object.keys(playersList).length){ + return false; + } + for(let i of clientsKeys){ + let otherPlayersData = Object.assign({}, playersList); + delete otherPlayersData[i]; + team.clients[i].send('*', {act: TeamsConst.ACTIONS.TEAM_UPDATE, players: otherPlayersData}); + } } static fetchPlayersData(team) @@ -32,24 +40,19 @@ class TeamUpdatesHandler static fetchPlayerProperties(playerSchema, sharedProperties) { let playerProperties = {}; - for(let propertyName of sharedProperties){ - let propertyPath = propertyName.split('/'); - // @TODO - HERE! - // playerProperties[propertyPath[propertyPath.length]] = sc.get(propertyPath, playerSchema); + for(let i of Object.keys(sharedProperties)){ + let propertyData = sharedProperties[i]; + playerProperties[i] = { + label: propertyData.label, + value: sc.getByPath(playerSchema, propertyData.path.split('/'), 0), + }; + if(propertyData.useMax){ + playerProperties[i].max = sc.getByPath(playerSchema, propertyData.useMax.split('/'), 0); + } } return playerProperties; } - static teamBroadcast(team, sendData) - { - for(let i of Object.keys(team.clients)){ - // @TODO - HERE! - // @TODO - BETA - Clone and remove current player from sendData. - team.clients[i].send('*', sendData); - } - } - - } module.exports.TeamUpdatesHandler = TeamUpdatesHandler; \ No newline at end of file diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 4778a65c1..fc7679494 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -43,7 +43,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveX', '5', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '5', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '5', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', 'stats/hp,stats/mp', @comma_separated_id); +INSERT INTO `config` VALUES (NULL, 'client', '{"hp":{"path":"stats/hp","useMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","useMax":"statsBase/mp","label":"MP"}}', @json_id); # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); diff --git a/package-lock.json b/package-lock.json index 812c428f1..74ceb9a3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "@reldens/modifiers": "^0.18.0", "@reldens/skills": "^0.17.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0", + "@reldens/utils": "^0.18.1", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", @@ -5503,9 +5503,9 @@ } }, "node_modules/@reldens/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-egOaCvMasNXLXlVNGA4Ngon0E9ngln7J9YIQZIHFhGGihgqdGppNewgAks+a/rQNR6x4E6109jpRcrlh7EHFUg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", "dependencies": { "await-event-emitter": "^2.0.2" } @@ -16439,9 +16439,9 @@ } }, "@reldens/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-egOaCvMasNXLXlVNGA4Ngon0E9ngln7J9YIQZIHFhGGihgqdGppNewgAks+a/rQNR6x4E6109jpRcrlh7EHFUg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", "requires": { "await-event-emitter": "^2.0.2" } diff --git a/package.json b/package.json index bb02e12b7..a8c02884b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@reldens/modifiers": "^0.18.0", "@reldens/skills": "^0.17.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0", + "@reldens/utils": "^0.18.1", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", From a856fe27995263629ffab873e1438fb80d3ea7e6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 30 Jan 2023 21:01:02 +0100 Subject: [PATCH 10/82] - Reldens - v4.0.0 - Team data broadcast and client responses. --- lib/game/client/room-events.js | 1 - lib/rooms/server/scene.js | 40 +++++++++---------- lib/teams/client/plugin.js | 32 ++------------- lib/teams/client/team-message-handler.js | 22 ++-------- lib/teams/constants.js | 6 +-- lib/teams/server/message-actions.js | 7 ++-- lib/teams/server/message-actions/team-join.js | 12 +++--- lib/teams/server/plugin.js | 11 +++-- .../server/subscribers/player-subscriber.js | 4 +- .../subscribers/stats-update-subscriber.js | 22 ++++++++++ lib/teams/server/team-updates-handler.js | 11 ++++- lib/users/server/plugin.js | 39 ++++++++++-------- .../teams/templates/team-player-data.html | 8 ++++ 13 files changed, 106 insertions(+), 109 deletions(-) create mode 100644 lib/teams/server/subscribers/stats-update-subscriber.js create mode 100644 theme/default/assets/features/teams/templates/team-player-data.html diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 4236ce2b3..bb8de5aa8 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -338,7 +338,6 @@ class RoomEvents roomOnLeave(code) { // @TODO - BETA - Improve disconnection handler. - // server disconnection handler: if(code > 1001 && !this.gameManager.gameOver && !this.gameManager.forcedDisconnection){ Logger.error('There was a connection error.', ['Error Code:', code]); } diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index b3768bb8f..7c4b9c0b5 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -374,18 +374,16 @@ class RoomScene extends RoomLogin async saveStateAndRemovePlayer(sessionId) { - // save the last state on the database: let savedPlayer = await this.savePlayerState(sessionId); - // first remove player body from current world: let playerSchema = this.getPlayerFromState(sessionId); - let isRemoveReady = true; + let stateObject = {isRemoveReady: true}; await this.events.emit('reldens.removeAllPlayerReferencesBefore', { room: this, savedPlayer, playerSchema, - isRemoveReady + stateObject }); - playerSchema && isRemoveReady + playerSchema && stateObject.isRemoveReady ? this.removeAllPlayerReferences(playerSchema, sessionId) : ErrorManager.error('Player not found, session ID: ' + sessionId); return savedPlayer; @@ -393,15 +391,11 @@ class RoomScene extends RoomLogin removeAllPlayerReferences(playerSchema, sessionId) { - // get body: let bodyToRemove = playerSchema.physicalBody; if(bodyToRemove){ - // remove body: this.roomWorld.removeBody(bodyToRemove); } - // remove the events: this.events.offByMasterKey(playerSchema.eventsPrefix + playerSchema.player_id); - // remove player: this.state.removePlayer(sessionId); } @@ -429,29 +423,31 @@ class RoomScene extends RoomLogin return playerSchema; } - async savePlayerStats(target, updateClient) + async savePlayerStats(playerSchema, client) { // @TODO - BETA - For now we are always updating all the stats but this can be improved to save only the ones // that changed. - // save the stats: - let updateReady = true; - this.events.emitSync('reldens.onSavePlayerStatsBefore', {room: this, target, updateClient, updateReady}); - if(!updateReady){ + let objectState = {updateReady: true}; + this.events.emitSync('reldens.onSavePlayerStatsBefore', {room: this, playerSchema, client, objectState}); + if(!objectState.updateReady){ return false; } - for(let i of Object.keys(target.stats)){ + for(let i of Object.keys(playerSchema.stats)){ let statId = this.config.client.players.initialStats[i].id; - // using a single update query we can easily update both value and base_value: let statPatch = { - value: target.stats[i], - base_value: target.statsBase[i] + value: playerSchema.stats[i], + base_value: playerSchema.statsBase[i] }; - await this.loginManager.usersManager.updatePlayerStatByIds(target.player_id, statId, statPatch); + await this.loginManager.usersManager.updatePlayerStatByIds(playerSchema.player_id, statId, statPatch); } - if(updateClient){ + if(client){ // @TODO - BETA - Convert all events in constants and consolidate them in a single file with descriptions. - await this.events.emit('reldens.savePlayerStatsUpdateClient', updateClient, target, this); - updateClient.send('*', {act: GameConst.PLAYER_STATS, stats: target.stats, statsBase: target.statsBase}); + await this.events.emit('reldens.savePlayerStatsUpdateClient', client, playerSchema, this); + client.send('*', { + act: GameConst.PLAYER_STATS, + stats: playerSchema.stats, + statsBase: playerSchema.statsBase + }); } return true; } diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 4163fd58e..ea5f31f53 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -33,43 +33,17 @@ class TeamsPlugin extends PluginInterface this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { this.teamTargetActions.showTeamInviteAction(this.gameManager, target, previousTarget, targetName); }); - this.events.on('reldens.playersOnAdd', (player, key, previousScene, roomEvents) => { - this.onPlayerAdd(key, roomEvents, player); - }); - /* - this.events.on('reldens.createUiScene', (preloadScene) => { - return this.onPreloadUiScene(preloadScene); - }); - */ this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } preloadTemplates(preloadScene) { let teamsTemplatePath = 'assets/features/teams/templates/'; - preloadScene.load.html(TeamsConst.KEY, teamsTemplatePath+'teams.html'); - preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'clan.html'); + preloadScene.load.html(TeamsConst.KEY, teamsTemplatePath+'ui-teams.html'); + preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'ui-clan.html'); preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); - } - - onPreloadUiScene(preloadScene) - { - // this.uiManager = new TeamsUi(preloadScene); - // this.uiManager.createUi(); - this.listenInventoryEvents(preloadScene); - } - - onPlayerAdd(key, roomEvents, player) - { - if(key !== roomEvents.room.sessionId){ - return false; - } - } - - listenInventoryEvents(uiScene, inventoryPanel, equipmentPanel) - { - + preloadScene.load.html('teamPlayerData', teamsTemplatePath+'team-player-data.html'); } } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 991bc9b1d..23a5c4a76 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -53,17 +53,8 @@ class TeamMessageHandler showTeamRequest() { - let teamUiKey = 'team'+this.message.id; + let teamUiKey = TeamsConst.KEY+this.message.id; let uiDoesNotExists = this.createTeamUi(teamUiKey); - /* - let acceptLabel = gameManager.config.getWithoutLogs( - 'client/team/titles/acceptFromPlayerLabel', - TeamsConst.LABELS.ACCEPT_FROM_PLAYER - ); - acceptFromPlayerLabel: acceptLabel.replace('%playerName', targetName), - yesLabel: gameManager.config.getWithoutLogs('client/team/titles/yesLabel', GameConst.LABELS.YES), - noLabel: gameManager.config.getWithoutLogs('client/team/titles/noLabel', GameConst.LABELS.NO), - */ this.roomEvents.initUi({ id: teamUiKey, title: this.gameManager.config.getWithoutLogs( @@ -79,7 +70,6 @@ class TeamMessageHandler }); if(uiDoesNotExists){ this.gameDom.getElement('#opt-2-'+teamUiKey)?.addEventListener('click', () => { - // send the close click action to the server: this.gameDom.getElement('#box-close-'+teamUiKey)?.click(); }); } @@ -92,7 +82,7 @@ class TeamMessageHandler let title = this.gameManager.config.getWithoutLogs( 'client/team/labels/leaderNameTitle', TeamsConst.LABELS.LEADER_NAME_TITLE - ).replace('%leaderName', this.message.with); + ).replace('%leaderName', this.message.leaderName); this.roomEvents.initUi({ id: teamUiKey, title, @@ -168,15 +158,11 @@ class TeamMessageHandler }); let disconfirmButton = this.gameManager.gameDom.getElement('.disconfirm-'+this.message.id); disconfirmButton?.addEventListener('click', () => { - this.gameManager.activeRoomEvents.room.send('*', { - act: TeamsConst.ACTIONS.TEAM_DECLINED, - id: this.message.id, - value: this.message.id - }); + this.gameDom.getElement('#box-close-'+TeamsConst.KEY+this.message.id)?.click(); }); let cancelButton = this.gameManager.gameDom.getElement('.cancel-'+this.message.id); cancelButton?.addEventListener('click', () => { - this.gameDom.getElement('#box-close-'+'team'+this.message.id)?.click(); + this.gameDom.getElement('#box-close-'+TeamsConst.KEY+this.message.id)?.click(); }); } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index a615c85a2..8fd2fa7bf 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -13,15 +13,13 @@ module.exports.TeamsConst = { ACTIONS: { TEAM_INVITE: pref+'inv', TEAM_ACCEPTED: pref+'acp', - TEAM_DECLINED: pref+'dec', TEAM_LEAVE: pref+'lev', TEAM_UPDATE: pref+'upd' }, LABELS: { INVITE_BUTTON_LABEL: 'Team - Invite', - TEAM_REQUEST_FROM: 'Team request from:', + TEAM_REQUEST_FROM: 'Accept team request from:', LEADER_NAME_TITLE: 'Team leader: %leaderName', - DISBAND: 'Disband Team', - ACCEPT_FROM_PLAYER: 'Accept team invite from player %playerName:' + DISBAND: 'Disband Team' } }; diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/message-actions.js index 370b308fd..51c9309e3 100644 --- a/lib/teams/server/message-actions.js +++ b/lib/teams/server/message-actions.js @@ -14,7 +14,7 @@ class TeamsMessageActions constructor(props) { - this.plugin = props.plugin; + this.teamsPlugin = props.teamsPlugin; } async executeMessageActions(client, data, room, playerSchema) @@ -30,12 +30,11 @@ class TeamsMessageActions return true; } if(TeamsConst.ACTIONS.TEAM_ACCEPTED === data.act && '1' === data.value){ - TeamJoin.execute(client, data, room, playerSchema, this.plugin); + TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); return true; } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - this.plugin.teams[data.id].leave(playerSchema); - delete this.plugin.playersTeamsRelation[playerSchema.id]; + this.teamsPlugin.teams[playerSchema.currentTeam].leave(playerSchema); return true; } } diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index 9a40d6223..e9dc4c393 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -6,7 +6,7 @@ const { Team } = require('../team'); const { TeamUpdatesHandler } = require('../team-updates-handler'); -const { Logger, sc } = require('@reldens/utils'); +const { Logger } = require('@reldens/utils'); class TeamJoin { @@ -18,9 +18,9 @@ class TeamJoin return false; } if(playerSchema.currentTeam){ - teamsPlugin.teams[playerSchema.currentTeam].leave(playerSchema); + teamsPlugin.teams[playerSchema.player_id]?.leave(playerSchema); } - let teamOwnerPlayer = room.state.players.get(data.id); + let teamOwnerPlayer = room.playerByIdFromState(data.id); if(!teamOwnerPlayer){ Logger.error('Player team owner not found.', teamOwnerPlayer, data); return false; @@ -30,14 +30,14 @@ class TeamJoin Logger.error('Player team owner client not found.', teamOwnerClient, data); return false; } - let currentTeam = teamsPlugin.teams[data.id] || new Team({ + let currentTeam = teamsPlugin.teams[teamOwnerClient.id] || new Team({ owner: teamOwnerPlayer, ownerClient: teamOwnerClient.client, sharedProperties: room.config.get('client/ui/teams/sharedProperties') }); currentTeam.join(playerSchema, client); - playerSchema.currentTeam = data.id; - teamsPlugin.playersTeamsRelation[playerSchema.id] = data.id; + playerSchema.currentTeam = teamOwnerClient.id; + teamsPlugin.teams[teamOwnerClient.id] = currentTeam; TeamUpdatesHandler.updateTeamPlayers(currentTeam); } diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index e3f2a2959..1e68d7a2e 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -7,6 +7,7 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamsMessageActions } = require('./message-actions'); const { PlayerSubscriber } = require('./subscribers/player-subscriber'); +const { StatsUpdateSubscriber } = require('./subscribers/stats-update-subscriber'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface @@ -19,13 +20,15 @@ class TeamsPlugin extends PluginInterface Logger.error('EventsManager undefined in TeamsPlugin.'); } this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { - roomMessageActions.teams = new TeamsMessageActions({plugin: this}); + roomMessageActions.teams = new TeamsMessageActions({teamsPlugin: this}); }); - this.events.on('reldens.createPlayerAfter', async (client, authResult, currentPlayer, room) => { - await PlayerSubscriber.enrichPlayerWithClan(client, currentPlayer, room, this.events, this.modelsManager); + this.events.on('reldens.createPlayerAfter', async (client, authResult, playerSchema, room) => { + await PlayerSubscriber.enrichPlayerWithClan(client, playerSchema, room, this.events, this.modelsManager); + }); + this.events.on('reldens.savePlayerStatsUpdateClient', async (client, playerSchema, room) => { + await StatsUpdateSubscriber.updateTeamData({teamsPlugin: this, playerSchema}); }); this.teams = {}; - this.playersTeamsRelation = {}; } } diff --git a/lib/teams/server/subscribers/player-subscriber.js b/lib/teams/server/subscribers/player-subscriber.js index 08f258ea8..722546970 100644 --- a/lib/teams/server/subscribers/player-subscriber.js +++ b/lib/teams/server/subscribers/player-subscriber.js @@ -7,7 +7,7 @@ class PlayerSubscriber { - static async enrichPlayerWithClan(client, currentPlayer, room, events, modelsManager) + static async enrichPlayerWithClan(client, playerSchema, room, events, modelsManager) { /* let serverProps = { @@ -21,7 +21,6 @@ class PlayerSubscriber groupClasses: room.config.getWithoutLogs('server/customClasses/inventory/groups', {}), itemsModelData: room.config.inventory.items }; - // @TODO - BETA - Add new event here for the server properties. let inventoryServer = new ItemsServer(serverProps); // broadcast player sessionId to share animations: inventoryServer.client.sendTargetProps.broadcast.push('sessionId'); @@ -37,7 +36,6 @@ class PlayerSubscriber owner: currentPlayer.inventory.manager.getOwnerId(), groups: room.config.get('inventory/groups/groupBaseData') }; - // @TODO - BETA - Add new event here for the sendData. client.send('*', sendData); */ } diff --git a/lib/teams/server/subscribers/stats-update-subscriber.js b/lib/teams/server/subscribers/stats-update-subscriber.js new file mode 100644 index 000000000..10775e4f8 --- /dev/null +++ b/lib/teams/server/subscribers/stats-update-subscriber.js @@ -0,0 +1,22 @@ +/** + * + * Reldens - StatsUpdateSubscriber + * + */ + +const { TeamUpdatesHandler } = require('../team-updates-handler'); + +class StatsUpdateSubscriber +{ + + static async updateTeamData(props) + { + let {teamsPlugin, playerSchema} = props; + return TeamUpdatesHandler.updateTeamPlayers( + teamsPlugin.teams[playerSchema.currentTeam]?.leave(playerSchema) + ); + } + +} + +module.exports.StatsUpdateSubscriber = StatsUpdateSubscriber; diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index bf24b461a..7a9bcf0da 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -23,8 +23,15 @@ class TeamUpdatesHandler for(let i of clientsKeys){ let otherPlayersData = Object.assign({}, playersList); delete otherPlayersData[i]; - team.clients[i].send('*', {act: TeamsConst.ACTIONS.TEAM_UPDATE, players: otherPlayersData}); + let sendUpdate = { + act: TeamsConst.ACTIONS.TEAM_UPDATE, + listener: TeamsConst.KEY, + players: otherPlayersData, + leaderName: team.owner.playerName, + }; + team.clients[i].send('*', sendUpdate); } + return true; } static fetchPlayersData(team) @@ -55,4 +62,4 @@ class TeamUpdatesHandler } -module.exports.TeamUpdatesHandler = TeamUpdatesHandler; \ No newline at end of file +module.exports.TeamUpdatesHandler = TeamUpdatesHandler; diff --git a/lib/users/server/plugin.js b/lib/users/server/plugin.js index fd3a2b1a5..8a96419c2 100644 --- a/lib/users/server/plugin.js +++ b/lib/users/server/plugin.js @@ -54,17 +54,18 @@ class UsersPlugin extends PluginInterface if(!this.lifeProp){ this.lifeProp = configProcessor.get('client/actions/skills/affectedProperty'); } - this.events.on('reldens.createPlayerStatsAfter', async (client, authResult, currentPlayer, roomScene) => { - await this.updatePlayersLifebar(roomScene, client, currentPlayer); + this.events.on('reldens.createPlayerStatsAfter', async (client, authResult, playerSchema, roomScene) => { + await this.updatePlayersLifebar(roomScene, client, playerSchema); await this.updateEnemiesLifebar(roomScene); }); - this.events.on('reldens.savePlayerStatsUpdateClient', async (client, target, roomScene) => { - await this.onSavePlayerStatsUpdateClient(client, target, roomScene); + this.events.on('reldens.savePlayerStatsUpdateClient', async (client, playerSchema, roomScene) => { + await this.onSavePlayerStatsUpdateClient(client, playerSchema, roomScene); }); this.events.on('reldens.runBattlePveAfter', async (event) => { return this.sendLifeBarUpdate(event); }); this.events.on('reldens.actionsPrepareEventsListeners', async (actionsPack, classPath) => { + // @TODO - BETA - Make sure lifebar is updated on every stats change and not only after damage was applied. classPath.listenEvent(SkillsEvents.SKILL_ATTACK_APPLY_DAMAGE, async (skill, target) => { let client = skill.owner.skillsServer.client.client; if(sc.hasOwn(target, 'player_id')){ @@ -101,11 +102,12 @@ class UsersPlugin extends PluginInterface this.broadcastObjectUpdate(roomScene, target) } - async updatePlayersLifebar(roomScene, client, currentPlayer) + async updatePlayersLifebar(roomScene, client, playerSchema) { - this.lifeBarConfig.showAllPlayers || this.lifeBarConfig.showOnClick - ? await this.updateAllPlayersLifeBars(roomScene) - : await this.onSavePlayerStatsUpdateClient(client, currentPlayer, roomScene); + if(this.lifeBarConfig.showAllPlayers || this.lifeBarConfig.showOnClick){ + return await this.updateAllPlayersLifeBars(roomScene); + } + return await this.onSavePlayerStatsUpdateClient(client, playerSchema, roomScene); } async updateEnemiesLifebar(roomScene) @@ -145,6 +147,7 @@ class UsersPlugin extends PluginInterface }; roomScene.broadcast('*', updateData); } + return true; } async preparePlayersStats(configProcessor) @@ -197,25 +200,29 @@ class UsersPlugin extends PluginInterface return {stats, statsBase}; } - async onSavePlayerStatsUpdateClient(client, target, roomScene) + async onSavePlayerStatsUpdateClient(client, playerSchema, roomScene) { if( - client.sessionId !== target.sessionId + client.sessionId !== playerSchema.sessionId && !this.lifeBarConfig.showAllPlayers && !this.lifeBarConfig.showOnClick ){ return false; } + // @TODO - BETA - Replace "oT", "oK" by constants. let updateData = { act: UsersConst.ACTION_LIFEBAR_UPDATE, oT: 'p', - oK: target.sessionId, - newValue: target.stats[this.lifeProp], - totalValue: target.statsBase[this.lifeProp] + oK: playerSchema.sessionId, + newValue: playerSchema.stats[this.lifeProp], + totalValue: playerSchema.statsBase[this.lifeProp] }; - this.lifeBarConfig.showAllPlayers || this.lifeBarConfig.showOnClick - ? roomScene.broadcast('*', updateData) - : client.send('*', updateData); + if(this.lifeBarConfig.showAllPlayers || this.lifeBarConfig.showOnClick){ + roomScene.broadcast('*', updateData); + return true; + } + client.send('*', updateData); + return true; } } diff --git a/theme/default/assets/features/teams/templates/team-player-data.html b/theme/default/assets/features/teams/templates/team-player-data.html new file mode 100644 index 000000000..30c559d88 --- /dev/null +++ b/theme/default/assets/features/teams/templates/team-player-data.html @@ -0,0 +1,8 @@ +
+
+ {{playerName}} +
+
+ {{playerProperties}} +
+
From e5c099fa7743ba1ec328980edf3149ec5c0ce884 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 3 Feb 2023 17:51:24 +0100 Subject: [PATCH 11/82] - Reldens - v4.0.0 - Teams clients sharing data. --- lib/chat/server/event-listener/npc-skills.js | 1 - lib/game/client/room-events.js | 10 ++- lib/teams/client/plugin.js | 20 ++--- lib/teams/client/team-message-handler.js | 89 +++++++++---------- lib/teams/client/templates-handler.js | 25 ++++++ lib/teams/constants.js | 3 +- lib/teams/server/message-actions/team-join.js | 1 + .../subscribers/stats-update-subscriber.js | 15 +++- lib/teams/server/team-updates-handler.js | 20 +++-- migrations/production/beta.26-sql-update.sql | 3 +- .../teams/templates/shared-property.html | 11 +++ .../teams/templates/team-container.html | 5 +- .../teams/templates/team-player-data.html | 5 +- theme/default/css/styles.scss | 1 + theme/default/css/teams.scss | 55 ++++++++++++ 15 files changed, 181 insertions(+), 83 deletions(-) create mode 100644 lib/teams/client/templates-handler.js create mode 100644 theme/default/assets/features/teams/templates/shared-property.html create mode 100644 theme/default/css/teams.scss diff --git a/lib/chat/server/event-listener/npc-skills.js b/lib/chat/server/event-listener/npc-skills.js index 1e668e20e..15244ae60 100644 --- a/lib/chat/server/event-listener/npc-skills.js +++ b/lib/chat/server/event-listener/npc-skills.js @@ -21,7 +21,6 @@ class NpcSkills this.listenDamageEvent(attackSkill, chatConfig, chatManager); this.listenModifiersEvent(effectSkill, chatConfig, chatManager); this.listenAfterRunLogicEvent((attackSkill || effectSkill), chatConfig, chatManager); - } static listenDamageEvent(attackSkill, chatConfig, chatManager) diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index bb8de5aa8..bae672194 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -402,26 +402,28 @@ class RoomEvents uiSetTitle(uiBox, props) { - if(!props.title){ + let newTitle = sc.get(props, 'title', false); + if(false === sc.get(props, 'title', false)){ return false; } let boxTitle = uiBox.getChildByProperty('className', 'box-title'); if(!boxTitle){ return false; } - boxTitle.innerHTML = props.title; + boxTitle.innerHTML = newTitle; } uiSetContent(uiBox, props, uiScene) { - if(!props.content){ + let newContent = sc.get(props, 'content', false); + if(false === newContent){ return false; } let boxContent = uiBox.getChildByProperty('className', 'box-content'); if(!boxContent){ return false; } - boxContent.innerHTML = props.content; + boxContent.innerHTML = newContent; this.uiSetContentOptions(uiScene, props, boxContent); } diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index ea5f31f53..0cd483826 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -8,27 +8,27 @@ const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetActions } = require('./team-create-target-action'); const { TeamMessageListener } = require('./team-message-listener'); +const { TemplatesHandler } = require('./templates-handler'); const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface { - setup(props) - { + setup(props) { this.teamTargetActions = new TeamTargetActions(); this.gameManager = sc.get(props, 'gameManager', false); // @NOTE: the tradeUi works as preload for the trade template which at the end is a dialog-box. this.teamsUi = new UserInterface(this.gameManager, {id: TeamsConst.KEY, type: TeamsConst.KEY}); - if(!this.gameManager){ + if (!this.gameManager) { Logger.error('Game Manager undefined in TeamsPlugin.'); } this.events = sc.get(props, 'events', false); - if(!this.events){ + if (!this.events) { Logger.error('EventsManager undefined in TeamsPlugin.'); } this.events.on('reldens.preloadUiScene', (preloadScene) => { - this.preloadTemplates(preloadScene); + TemplatesHandler.preloadTemplates(preloadScene); }); this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { this.teamTargetActions.showTeamInviteAction(this.gameManager, target, previousTarget, targetName); @@ -36,16 +36,6 @@ class TeamsPlugin extends PluginInterface this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } - preloadTemplates(preloadScene) - { - let teamsTemplatePath = 'assets/features/teams/templates/'; - preloadScene.load.html(TeamsConst.KEY, teamsTemplatePath+'ui-teams.html'); - preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'ui-clan.html'); - preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); - preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); - preloadScene.load.html('teamPlayerData', teamsTemplatePath+'team-player-data.html'); - } - } module.exports.TeamsPlugin = TeamsPlugin; diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 23a5c4a76..1b603d843 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -54,7 +54,7 @@ class TeamMessageHandler showTeamRequest() { let teamUiKey = TeamsConst.KEY+this.message.id; - let uiDoesNotExists = this.createTeamUi(teamUiKey); + let teamsUi = this.createTeamUi(teamUiKey); this.roomEvents.initUi({ id: teamUiKey, title: this.gameManager.config.getWithoutLogs( @@ -68,7 +68,7 @@ class TeamMessageHandler id: this.message.id } }); - if(uiDoesNotExists){ + if(teamsUi){ this.gameDom.getElement('#opt-2-'+teamUiKey)?.addEventListener('click', () => { this.gameDom.getElement('#box-close-'+teamUiKey)?.click(); }); @@ -82,7 +82,7 @@ class TeamMessageHandler let title = this.gameManager.config.getWithoutLogs( 'client/team/labels/leaderNameTitle', TeamsConst.LABELS.LEADER_NAME_TITLE - ).replace('%leaderName', this.message.leaderName); + ).replace('%leaderName', this.message.leaderName); this.roomEvents.initUi({ id: teamUiKey, title, @@ -95,13 +95,14 @@ class TeamMessageHandler return false; } let players = sc.get(this.message, 'players', false); + console.log({message: this.message}); this.updateTeamBox(players, container); } createTeamUi(teamUiKey) { - let uiDoesNotExists = !sc.hasOwn(this.roomEvents.teamUi, teamUiKey); - if(uiDoesNotExists){ + let teamsUi = sc.hasOwn(this.roomEvents.teamUi, teamUiKey); + if(!teamsUi){ this.roomEvents.teamUi[teamUiKey] = new UserInterface( this.gameManager, {id: teamUiKey, type: TeamsConst.KEY}, @@ -110,12 +111,13 @@ class TeamMessageHandler ); this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); } - return uiDoesNotExists; + return teamsUi; } updateTeamBox(players, container) { - if(!this.message.data){ + if(!players){ + Logger.error('Players not defined.', players); return; } let teamMembers = ''; @@ -137,7 +139,7 @@ class TeamMessageHandler } let templateParams = { teamActionKey: this.message.id, - disbandLabel: this.gameManager.config.getWithoutLogs( + disbandActionLabel: this.gameManager.config.getWithoutLogs( 'client/team/titles/disbandLabel', TeamsConst.LABELS.DISBAND ), @@ -175,46 +177,46 @@ class TeamMessageHandler return ''; } return this.gameManager.gameEngine.parseTemplate(messageTemplate, { - id: playerData.player_id, - name: playerData.name, - teamAction: this.createTeamActionContent(playerData) + playerId: playerData.id, + playerName: playerData.name, + playerProperties: this.createSharedPropertiesContent(playerData.sharedProperties) }); } - createTeamActionContent(item, teamAction) + createSharedPropertiesContent(playerSharedProperties) { - // @TODO - BETA - Move the template load from cache as part of the engine driver. - let messageTemplate = this.uiScene.cache.html.get('inventoryTeamAction'); + let messageTemplate = this.uiScene.cache.html.get('teamsSharedProperty'); if(!messageTemplate){ - Logger.error('Missing template "inventoryTeamAction".'); + Logger.error('Missing template "teamsSharedProperty".'); return ''; } - return this.gameManager.gameEngine.parseTemplate(messageTemplate, { - key: item.key, - id: item.getInventoryId(), - teamAction: teamAction || sc.get(item, 'teamAction', '') - }); - } - - createTeamActionRemove(item) - { + let sharedPropertiesContent = ''; // @TODO - BETA - Move the template load from cache as part of the engine driver. - let messageTemplate = this.uiScene.cache.html.get('inventoryTeamActionRemove'); - if(!messageTemplate){ - Logger.error('Missing template "inventoryTeamActionRemove".'); - return ''; + for(let i of Object.keys(playerSharedProperties)) { + messageTemplate = this.uiScene.cache.html.get('teamsSharedProperty'); + let propertyData = playerSharedProperties[i]; + let propertyMaxValue = sc.get(propertyData, 'max', ''); + if('' !== propertyMaxValue){ + propertyMaxValue = this.gameManager.config.getWithoutLogs( + 'client/team/labels/propertyMaxValue', + TeamsConst.LABELS.PROPERTY_MAX_VALUE + ).replace('%propertyMaxValue', propertyMaxValue); + } + sharedPropertiesContent += this.gameManager.gameEngine.parseTemplate(messageTemplate, { + key: i, + label: propertyData.label, + value: propertyData.value, + max: propertyMaxValue + }); } - return this.gameManager.gameEngine.parseTemplate(messageTemplate, { - key: item.key, - id: item.uid, - teamAction: 'remove' - }); + return sharedPropertiesContent; } - activateTeamTargetActions(items) + activateTeamTargetActions(playersData) { - for(let i of Object.keys(items)){ - let item = items[i]; + for(let i of Object.keys(playersData)){ + let player = playersData[i]; + /* let itemContainerSelector = '.team-item-to-be-'+item.teamAction+'.team-item-'+item.uid +' .team-action-'+item.teamAction; let itemButtonSelector = itemContainerSelector+' button'; @@ -224,19 +226,10 @@ class TeamMessageHandler continue; } itemActionButton.addEventListener('click', () => { - let qtySelector = this.gameDom.getElement('.team-item-'+item.getInventoryId()+' .item-qty input'); - let qtySelected = qtySelector?.value || 1; - let dataSend = { - act: TeamsConst.ACTIONS.TEAM_ACTION, - id: this.message.id, - value: item.teamAction, - itemId: item.getInventoryId(), - itemKey: item.key, - qty: Number(qtySelected) - }; - dataSend[ObjectsConst.TEAM_ACTIONS.SUB_ACTION] = ObjectsConst.TEAM_ACTIONS.ADD; - this.gameManager.activeRoomEvents.room.send('*', dataSend); + // @TODO - HEREEEEEEEEEEEEEEEEEE! Send TARGET ACTION. + // this.gameManager.activeRoomEvents.room.send('*', dataSend); }); + */ } } diff --git a/lib/teams/client/templates-handler.js b/lib/teams/client/templates-handler.js new file mode 100644 index 000000000..42f98bc67 --- /dev/null +++ b/lib/teams/client/templates-handler.js @@ -0,0 +1,25 @@ +/** + * + * Reldens - TemplatesHandler + * + */ + +const { TeamsConst } = require('../constants'); + +class TemplatesHandler +{ + + static preloadTemplates(preloadScene) + { + let teamsTemplatePath = 'assets/features/teams/templates/'; + preloadScene.load.html(TeamsConst.KEY, teamsTemplatePath+'ui-teams.html'); + preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'ui-clan.html'); + preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); + preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); + preloadScene.load.html('teamContainer', teamsTemplatePath+'team-container.html'); + preloadScene.load.html('teamPlayerData', teamsTemplatePath+'team-player-data.html'); + preloadScene.load.html('teamsSharedProperty', teamsTemplatePath+'shared-property.html'); + } + +} +module.exports.TemplatesHandler = TemplatesHandler; \ No newline at end of file diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 8fd2fa7bf..d81994d5d 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -20,6 +20,7 @@ module.exports.TeamsConst = { INVITE_BUTTON_LABEL: 'Team - Invite', TEAM_REQUEST_FROM: 'Accept team request from:', LEADER_NAME_TITLE: 'Team leader: %leaderName', - DISBAND: 'Disband Team' + DISBAND: 'Disband Team', + PROPERTY_MAX_VALUE: '/ %propertyMaxValue' } }; diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index e9dc4c393..2ba2d2800 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -36,6 +36,7 @@ class TeamJoin sharedProperties: room.config.get('client/ui/teams/sharedProperties') }); currentTeam.join(playerSchema, client); + teamOwnerPlayer.currentTeam = teamOwnerClient.id; playerSchema.currentTeam = teamOwnerClient.id; teamsPlugin.teams[teamOwnerClient.id] = currentTeam; TeamUpdatesHandler.updateTeamPlayers(currentTeam); diff --git a/lib/teams/server/subscribers/stats-update-subscriber.js b/lib/teams/server/subscribers/stats-update-subscriber.js index 10775e4f8..feac06c66 100644 --- a/lib/teams/server/subscribers/stats-update-subscriber.js +++ b/lib/teams/server/subscribers/stats-update-subscriber.js @@ -5,6 +5,7 @@ */ const { TeamUpdatesHandler } = require('../team-updates-handler'); +const { Logger, sc } = require('@reldens/utils'); class StatsUpdateSubscriber { @@ -12,9 +13,17 @@ class StatsUpdateSubscriber static async updateTeamData(props) { let {teamsPlugin, playerSchema} = props; - return TeamUpdatesHandler.updateTeamPlayers( - teamsPlugin.teams[playerSchema.currentTeam]?.leave(playerSchema) - ); + let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); + if('' === currentTeamId){ + Logger.info('Team ID not present.', currentTeamId, playerSchema.currentTeam); + return; + } + let currentTeam = sc.get(teamsPlugin.teams, currentTeamId, false); + if(!currentTeam){ + Logger.error('Team not found: '+ currentTeamId); + return; + } + return TeamUpdatesHandler.updateTeamPlayers(teamsPlugin.teams[playerSchema.currentTeam]); } } diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 7a9bcf0da..6227d6f36 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -25,6 +25,7 @@ class TeamUpdatesHandler delete otherPlayersData[i]; let sendUpdate = { act: TeamsConst.ACTIONS.TEAM_UPDATE, + id: team.ownerClient.id, listener: TeamsConst.KEY, players: otherPlayersData, leaderName: team.owner.playerName, @@ -39,25 +40,30 @@ class TeamUpdatesHandler let teamPlayersId = Object.keys(team.players); let playersData = {}; for(let i of teamPlayersId){ - playersData[i] = this.fetchPlayerProperties(team.players[i], team.sharedProperties); + playersData[i] = this.fetchPlayerData(team.players[i], team.sharedProperties); } return playersData; } - static fetchPlayerProperties(playerSchema, sharedProperties) + static fetchPlayerData(playerSchema, sharedProperties) { - let playerProperties = {}; + let playerData = { + name: playerSchema.playerName, + id: playerSchema.player_id, + sharedProperties: {} + }; for(let i of Object.keys(sharedProperties)){ let propertyData = sharedProperties[i]; - playerProperties[i] = { + playerData.sharedProperties[i] = { label: propertyData.label, value: sc.getByPath(playerSchema, propertyData.path.split('/'), 0), }; - if(propertyData.useMax){ - playerProperties[i].max = sc.getByPath(playerSchema, propertyData.useMax.split('/'), 0); + let pathMax = sc.get(propertyData, 'pathMax', ''); + if('' !== pathMax){ + playerData.sharedProperties[i].max = sc.getByPath(playerSchema, pathMax.split('/'), 0); } } - return playerProperties; + return playerData; } } diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index fc7679494..37f59e4e4 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -33,6 +33,7 @@ UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; INSERT INTO `config` VALUES (NULL, 'client', 'ui/options/acceptOrDecline', '{"1":{"label":"Accept","value":1},"2":{"label":"Decline","value":2}}', @json_id); INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/leaderNameTitle', 'Team leader: %leaderName', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; @@ -43,7 +44,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveX', '5', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '5', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '5', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', '{"hp":{"path":"stats/hp","useMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","useMax":"statsBase/mp","label":"MP"}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); diff --git a/theme/default/assets/features/teams/templates/shared-property.html b/theme/default/assets/features/teams/templates/shared-property.html new file mode 100644 index 000000000..dca0ba533 --- /dev/null +++ b/theme/default/assets/features/teams/templates/shared-property.html @@ -0,0 +1,11 @@ +
+
+ {{label}} +
+
+ {{value}} +
+
+ {{max}} +
+
diff --git a/theme/default/assets/features/teams/templates/team-container.html b/theme/default/assets/features/teams/templates/team-container.html index 3f2dbc93f..c1ba000c2 100644 --- a/theme/default/assets/features/teams/templates/team-container.html +++ b/theme/default/assets/features/teams/templates/team-container.html @@ -1,9 +1,10 @@
+ {{&teamMembers}}
-
- +
+
diff --git a/theme/default/assets/features/teams/templates/team-player-data.html b/theme/default/assets/features/teams/templates/team-player-data.html index 30c559d88..f4e21ca0f 100644 --- a/theme/default/assets/features/teams/templates/team-player-data.html +++ b/theme/default/assets/features/teams/templates/team-player-data.html @@ -2,7 +2,10 @@
{{playerName}}
+
+ remove +
- {{playerProperties}} + {{&playerProperties}}
diff --git a/theme/default/css/styles.scss b/theme/default/css/styles.scss index 122eaa192..fa59db5e1 100644 --- a/theme/default/css/styles.scss +++ b/theme/default/css/styles.scss @@ -22,3 +22,4 @@ $cBlack: #000; @import "firebase"; @import "chat"; @import "items-system"; +@import "teams"; diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss new file mode 100644 index 000000000..77fb14255 --- /dev/null +++ b/theme/default/css/teams.scss @@ -0,0 +1,55 @@ +.team-player, +.property-box, +.properties-list-container { + width: 100%; + float: left; + display: block; + position: relative; + padding: 0; + margin: 0 0 5px; +} + +.player-name { + float: left; + display: block; + width: 90%; + text-align: left; +} + +.team-remove-container { + float: left; + display: block; + width: 10%; + + #team-remove { + position: absolute; + top: 0; + right: 0; + max-width: 24px; + cursor: pointer; + } + +} + +.property-box { + float: left; + padding: 0; + margin: 0; + + div { + float: left; + } + + .label { + margin-right: 10px; + } + + .value { + margin-right: 6px; + } + +} + +.team-disband-action { + float: right; +} \ No newline at end of file From 8003dfd06bc5136078dbfe1dfe7009cded763350 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 14 Feb 2023 16:16:59 +0100 Subject: [PATCH 12/82] - Reldens - v4.0.0 - Teams UI. --- lib/game/client/ui-factory.js | 1 - lib/game/client/user-interface.js | 11 +- .../client/prediction-world-creator.js | 4 - lib/teams/client/plugin.js | 5 +- lib/teams/client/team-create-target-action.js | 1 + lib/teams/client/team-message-handler.js | 124 +++++++++--------- lib/teams/constants.js | 1 + lib/teams/server/team-updates-handler.js | 1 + lib/users/client/player-engine.js | 17 ++- lib/world/server/p2world.js | 3 - .../features/inventory/assets/equipment.png | Bin 5413 -> 3021 bytes .../features/inventory/assets/equipped.png | Bin 1058 -> 2153 bytes .../features/inventory/assets/inventory.png | Bin 3750 -> 2915 bytes .../assets/features/inventory/assets/team.png | Bin 0 -> 2248 bytes .../features/inventory/assets/trash.png | Bin 1042 -> 2073 bytes .../features/inventory/assets/unequipped.png | Bin 1079 -> 1899 bytes .../assets/features/inventory/assets/use.png | Bin 1117 -> 1973 bytes .../assets/features/teams/assets/team.png | Bin 0 -> 29567 bytes .../teams/templates/team-container.html | 4 +- .../teams/templates/team-player-data.html | 4 +- .../features/teams/templates/team-remove.html | 3 + .../features/teams/templates/ui-teams.html | 8 +- theme/default/assets/icons/book.png | Bin 2297 -> 3266 bytes theme/default/assets/icons/instructions.png | Bin 5450 -> 3924 bytes theme/default/assets/icons/minimap.png | Bin 3288 -> 1806 bytes theme/default/css/teams.scss | 5 + 26 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 theme/default/assets/features/inventory/assets/team.png create mode 100644 theme/default/assets/features/teams/assets/team.png create mode 100644 theme/default/assets/features/teams/templates/team-remove.html diff --git a/lib/game/client/ui-factory.js b/lib/game/client/ui-factory.js index af0ac6b7f..0e917010e 100644 --- a/lib/game/client/ui-factory.js +++ b/lib/game/client/ui-factory.js @@ -5,7 +5,6 @@ */ const { GameConst } = require('../constants'); -const { sc } = require('@reldens/utils'); class UiFactory { diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index eeb076f7e..d25fe22fa 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -7,6 +7,7 @@ */ const { GameConst } = require('../constants'); +const { Logger, sc } = require('@reldens/utils'); class UserInterface { @@ -39,10 +40,10 @@ class UserInterface templateKey = this.id; } let objectElementId = 'box-'+this.id; - let exists = uiScene.gameManager.gameDom.getElement('#'+objectElementId); + let gameDom = uiScene.gameManager.gameDom; + let exists = gameDom.getElement('#'+objectElementId); if(exists){ - // avoid duplicated elements: - return true; + return sc.get(uiScene.userInterfaces, this.id, false); } let {newWidth, newHeight} = uiScene.gameManager.gameEngine.getCurrentScreenSize(uiScene.gameManager); let {uiX, uiY} = uiScene.getUiPosition(this.uiPositionKey, newWidth, newHeight); @@ -53,6 +54,10 @@ class UserInterface content: this.initialContent }); let dialogContainer = dialogBox.getChildByProperty('className', 'ui-box ui-dialog-box'); + if(!dialogContainer){ + Logger.critical('Missing dialog container for template key: "'+templateKey+'".'); + return false; + } dialogContainer.id = objectElementId; dialogContainer.classList.add('type-'+(this.animProps?.type || 'dialog-box')); let boxClose = dialogBox.getChildByProperty('className', 'box-close'); diff --git a/lib/prediction/client/prediction-world-creator.js b/lib/prediction/client/prediction-world-creator.js index fc0e5c5c2..d57a85d58 100644 --- a/lib/prediction/client/prediction-world-creator.js +++ b/lib/prediction/client/prediction-world-creator.js @@ -13,10 +13,6 @@ const { Logger, sc } = require('@reldens/utils'); class PredictionWorldCreator { - /** - * @param {SceneDynamic} scene - * @returns {Promise} - */ async createSceneWorld(scene) { if(!scene.experimentalClientPrediction){ diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 0cd483826..094b4dc50 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -4,7 +4,7 @@ * */ -const { UserInterface } = require('../../game/client/user-interface'); +// const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetActions } = require('./team-create-target-action'); const { TeamMessageListener } = require('./team-message-listener'); @@ -18,8 +18,7 @@ class TeamsPlugin extends PluginInterface setup(props) { this.teamTargetActions = new TeamTargetActions(); this.gameManager = sc.get(props, 'gameManager', false); - // @NOTE: the tradeUi works as preload for the trade template which at the end is a dialog-box. - this.teamsUi = new UserInterface(this.gameManager, {id: TeamsConst.KEY, type: TeamsConst.KEY}); + // this.clanUi = new UserInterface(this.gameManager, {id: TeamsConst.CLAN_KEY, type: TeamsConst.CLAN_KEY}); if (!this.gameManager) { Logger.error('Game Manager undefined in TeamsPlugin.'); } diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 5c89853a6..21df4a864 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -13,6 +13,7 @@ class TeamTargetActions showTeamInviteAction(gameManager, target, previousTarget, targetName) { + // @TODO - Return false if player is already in the current team. if(GameConst.TYPE_PLAYER !== target.type || gameManager.getCurrentPlayer().playerId === target.id){ return false; } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 1b603d843..abe419fe9 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -4,10 +4,9 @@ * */ -const { ErrorManager, Logger, sc } = require('@reldens/utils'); -const { TeamsConst } = require('../constants'); -const { ObjectsConst } = require('../../objects/constants'); const { UserInterface } = require('../../game/client/user-interface'); +const { TeamsConst } = require('../constants'); +const { ErrorManager, Logger, sc } = require('@reldens/utils'); class TeamMessageHandler { @@ -34,7 +33,7 @@ class TeamMessageHandler ErrorManager.error('Missing GameManager.'); } if(!this.uiScene){ - ErrorManager.error('Missing UiScene.'); + ErrorManager.error('Missing UI Scene.'); } } @@ -43,20 +42,19 @@ class TeamMessageHandler if(TeamsConst.ACTIONS.TEAM_INVITE === this.message.act){ return this.showTeamRequest(); } - if( - TeamsConst.ACTIONS.TEAM_ACCEPTED === this.message.act - || TeamsConst.ACTIONS.TEAM_UPDATE === this.message.act - ){ + if(TeamsConst.ACTIONS.TEAM_UPDATE === this.message.act){ + if(!this.gameManager.gameDom.getElement('#box-'+this.teamUiKey()+' .box-content')){ + this.gameManager.gameEngine.clearTarget(); + } return this.showTeamBox(); } } showTeamRequest() { - let teamUiKey = TeamsConst.KEY+this.message.id; - let teamsUi = this.createTeamUi(teamUiKey); + let teamsUi = this.createTeamUi(this.teamUiKey()); this.roomEvents.initUi({ - id: teamUiKey, + id: this.teamUiKey(), title: this.gameManager.config.getWithoutLogs( 'client/teams/labels/requestFromTitle', TeamsConst.LABELS.TEAM_REQUEST_FROM @@ -69,15 +67,20 @@ class TeamMessageHandler } }); if(teamsUi){ - this.gameDom.getElement('#opt-2-'+teamUiKey)?.addEventListener('click', () => { - this.gameDom.getElement('#box-close-'+teamUiKey)?.click(); + this.gameDom.getElement('#opt-2-'+this.teamUiKey())?.addEventListener('click', () => { + this.gameDom.getElement('#box-close-'+this.teamUiKey())?.click(); }); } } + teamUiKey() + { + return TeamsConst.KEY + this.message.id; + } + showTeamBox() { - let teamUiKey = TeamsConst.KEY+this.message.id; + let teamUiKey = this.teamUiKey(); this.createTeamUi(teamUiKey); let title = this.gameManager.config.getWithoutLogs( 'client/team/labels/leaderNameTitle', @@ -106,12 +109,12 @@ class TeamMessageHandler this.roomEvents.teamUi[teamUiKey] = new UserInterface( this.gameManager, {id: teamUiKey, type: TeamsConst.KEY}, - 'assets/html/dialog-box.html', + 'assets/features/teams/templates/ui-teams.html', TeamsConst.KEY ); this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); } - return teamsUi; + return this.roomEvents.teamUi[teamUiKey]; } updateTeamBox(players, container) @@ -132,68 +135,74 @@ class TeamMessageHandler createTeamContainer(teamMembers) { // @TODO - BETA - Move the template load from cache as part of the engine driver. - let messageTemplate = this.uiScene.cache.html.get('teamContainer'); - if(!messageTemplate){ + let templateContent = this.uiScene.cache.html.get('teamContainer'); + if(!templateContent){ Logger.error('Missing template "teamContainer".'); return ''; } + let playerId = this.gameManager.getCurrentPlayer().playerId; + let isPlayerOwner = playerId === this.message.id; + let leaveActionLabel = isPlayerOwner + ? this.gameManager.config.getWithoutLogs('client/team/titles/disbandLabel', TeamsConst.LABELS.DISBAND) + : this.gameManager.config.getWithoutLogs('client/team/titles/leaveLabel', TeamsConst.LABELS.LEAVE); let templateParams = { - teamActionKey: this.message.id, - disbandActionLabel: this.gameManager.config.getWithoutLogs( - 'client/team/titles/disbandLabel', - TeamsConst.LABELS.DISBAND - ), + teamId: this.message.id, + playerId, + leaveActionLabel: leaveActionLabel, teamMembers }; - return this.gameManager.gameEngine.parseTemplate(messageTemplate, templateParams); + return this.gameManager.gameEngine.parseTemplate(templateContent, templateParams); } activateTeamLeaveButtonAction() { - let confirmButton = this.gameManager.gameDom.getElement('.confirm-'+this.message.id); - confirmButton?.addEventListener('click', () => { + let leaveButton = this.gameManager.gameDom.getElement('.leave-'+this.message.id); + leaveButton?.addEventListener('click', () => { this.gameManager.activeRoomEvents.room.send('*', { - act: TeamsConst.ACTIONS.TEAM_ACCEPTED, - id: this.message.id, - value: this.message.id + act: TeamsConst.ACTIONS.TEAM_LEAVE, + id: this.message.id }); }); - let disconfirmButton = this.gameManager.gameDom.getElement('.disconfirm-'+this.message.id); - disconfirmButton?.addEventListener('click', () => { - this.gameDom.getElement('#box-close-'+TeamsConst.KEY+this.message.id)?.click(); - }); - let cancelButton = this.gameManager.gameDom.getElement('.cancel-'+this.message.id); - cancelButton?.addEventListener('click', () => { - this.gameDom.getElement('#box-close-'+TeamsConst.KEY+this.message.id)?.click(); - }); } createTeamMemberBox(playerData) { // @TODO - BETA - Move the template load from cache as part of the engine driver. - let messageTemplate = this.uiScene.cache.html.get('teamPlayerData'); - if(!messageTemplate){ + let templateContent = this.uiScene.cache.html.get('teamPlayerData'); + if(!templateContent){ Logger.error('Missing template "teamPlayerData".'); return ''; } - return this.gameManager.gameEngine.parseTemplate(messageTemplate, { + let isPlayerOwner = this.gameManager.getCurrentPlayer().playerId === this.message.id; + return this.gameManager.gameEngine.parseTemplate(templateContent, { playerId: playerData.id, playerName: playerData.name, - playerProperties: this.createSharedPropertiesContent(playerData.sharedProperties) + playerProperties: this.createSharedPropertiesContent(playerData.sharedProperties), + playerRemove: isPlayerOwner ? this.createDismissPlayerButton(playerData) : '' }); } + createDismissPlayerButton(playerData) + { + let templateContent = this.uiScene.cache.html.get('teamRemove'); + if(!templateContent){ + Logger.error('Missing template "teamRemove".'); + return ''; + } + return this.gameManager.gameEngine.parseTemplate(templateContent, {playerId: playerData.id}); + } + createSharedPropertiesContent(playerSharedProperties) { - let messageTemplate = this.uiScene.cache.html.get('teamsSharedProperty'); - if(!messageTemplate){ + let templateContent = this.uiScene.cache.html.get('teamsSharedProperty'); + if(!templateContent){ Logger.error('Missing template "teamsSharedProperty".'); return ''; } let sharedPropertiesContent = ''; // @TODO - BETA - Move the template load from cache as part of the engine driver. for(let i of Object.keys(playerSharedProperties)) { - messageTemplate = this.uiScene.cache.html.get('teamsSharedProperty'); + templateContent = this.uiScene.cache.html.get('teamsSharedProperty'); let propertyData = playerSharedProperties[i]; let propertyMaxValue = sc.get(propertyData, 'max', ''); if('' !== propertyMaxValue){ @@ -202,7 +211,7 @@ class TeamMessageHandler TeamsConst.LABELS.PROPERTY_MAX_VALUE ).replace('%propertyMaxValue', propertyMaxValue); } - sharedPropertiesContent += this.gameManager.gameEngine.parseTemplate(messageTemplate, { + sharedPropertiesContent += this.gameManager.gameEngine.parseTemplate(templateContent, { key: i, label: propertyData.label, value: propertyData.value, @@ -215,24 +224,19 @@ class TeamMessageHandler activateTeamTargetActions(playersData) { for(let i of Object.keys(playersData)){ - let player = playersData[i]; - /* - let itemContainerSelector = '.team-item-to-be-'+item.teamAction+'.team-item-'+item.uid - +' .team-action-'+item.teamAction; - let itemButtonSelector = itemContainerSelector+' button'; - let itemActionButton = this.gameDom.getElement(itemButtonSelector); - if(!itemActionButton){ - Logger.error('Activate team item "'+item.uid+'" action button not found.'); - continue; - } - itemActionButton.addEventListener('click', () => { - // @TODO - HEREEEEEEEEEEEEEEEEEE! Send TARGET ACTION. - // this.gameManager.activeRoomEvents.room.send('*', dataSend); + let playerData = playersData[i]; + let selectorPlayerName = '.team-player-'+i+' .player-name'; + let selectorPlayerProperties = '.team-player-'+i+' .properties-list-container'; + console.log({i, selectorPlayerName, selectorPlayerProperties, playerData}); + this.gameDom.getElement(selectorPlayerName).addEventListener('click', () => { + this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); + }); + this.gameDom.getElement(selectorPlayerProperties).addEventListener('click', () => { + this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); - */ } } } -module.exports.TeamMessageHandler = TeamMessageHandler; \ No newline at end of file +module.exports.TeamMessageHandler = TeamMessageHandler; diff --git a/lib/teams/constants.js b/lib/teams/constants.js index d81994d5d..c5ce705e2 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -21,6 +21,7 @@ module.exports.TeamsConst = { TEAM_REQUEST_FROM: 'Accept team request from:', LEADER_NAME_TITLE: 'Team leader: %leaderName', DISBAND: 'Disband Team', + LEAVE: 'Leave Team', PROPERTY_MAX_VALUE: '/ %propertyMaxValue' } }; diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 6227d6f36..144242b76 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -50,6 +50,7 @@ class TeamUpdatesHandler let playerData = { name: playerSchema.playerName, id: playerSchema.player_id, + sessionId: playerSchema.sessionId, sharedProperties: {} }; for(let i of Object.keys(sharedProperties)){ diff --git a/lib/users/client/player-engine.js b/lib/users/client/player-engine.js index 4ae52b03d..976772aa5 100644 --- a/lib/users/client/player-engine.js +++ b/lib/users/client/player-engine.js @@ -64,7 +64,7 @@ class PlayerEngine addPlayer(id, addPlayerData) { - // @TODO - BETA - Create a PlayersManager attached to the Scene and move all the CRUD methods into it. + // @TODO - BETA - Create a PlayersManager attached to the Scene and move all the players handler methods there. if(sc.hasOwn(this.players, id)){ // player sprite already exists, update it and return it: return this.players[id]; @@ -97,12 +97,21 @@ class PlayerEngine // @NOTE: we could send a specific action when the player has been targeted. // this.room.send('*', {act: GameConst.TYPE_PLAYER, id: id}); // update target ui: - let previousTarget = Object.assign({}, this.currentTarget); - this.currentTarget = {id: id, type: GameConst.TYPE_PLAYER}; - this.gameManager.gameEngine.showTarget(this.players[id].playerName, this.currentTarget, previousTarget); + this.setTargetPlayerById(id); }); } + setTargetPlayerById(id) + { + if(sc.get(this.players, 'id', false)){ + this.gameManager.gameEngine.clearTarget(); + return false; + } + let previousTarget = Object.assign({}, this.currentTarget); + this.currentTarget = {id: id, type: GameConst.TYPE_PLAYER}; + this.gameManager.gameEngine.showTarget(this.players[id].playerName, this.currentTarget, previousTarget); + } + showPlayerName(id) { if(!this.globalConfigShowNames){ diff --git a/lib/world/server/p2world.js b/lib/world/server/p2world.js index 5cd4b3df7..5a2b678e2 100644 --- a/lib/world/server/p2world.js +++ b/lib/world/server/p2world.js @@ -241,11 +241,8 @@ class P2world extends World } // by default objects won't have mass: let bodyMass = sc.get(roomObject, 'bodyMass', 0); - // by default objects collision response: let collision = sc.hasOwn(roomObject, 'collisionResponse', false); - // object state: let hasState = sc.get(roomObject, 'hasState', false); - // create the body: let bodyObject = this.createCollisionBody(tileW, tileH, posX, posY, bodyMass, collision, hasState, objectIndex); bodyObject.isRoomObject = true; // assign the room object to the body: diff --git a/theme/default/assets/features/inventory/assets/equipment.png b/theme/default/assets/features/inventory/assets/equipment.png index 58045ad340fb71077e133371f8a194f8586c639f..a79c066b6f600fb243a9c47fe20da9eed899078c 100644 GIT binary patch delta 2938 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 O4wR0OcExQw%>My(h}lB` delta 5349 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 Dts)eb diff --git a/theme/default/assets/features/inventory/assets/equipped.png b/theme/default/assets/features/inventory/assets/equipped.png index 67c50201f1431a1bd99c3a6c615944c8c406c83d..b0b439a3974e66fde0e3628b71b77598bc286996 100644 GIT binary patch literal 2153 zcmV-v2$uJWP)2e#Xo{BBGT5(o%Gf zF!SdCI+^*CNF?&<{rmT0W_TDD;P?Bpg%EEt^FAUfG81$*nE5_{_ccxXAQ%je8}T(H zz~}S*N(k}WsU`Bl!OXo<%6C;&{U}wRlmvLaUbmtse1ODe$U^KBABTq7da3ul-Dn0Z)Axkpvi z+wIq@=`7MDKk&v{DBVy;o9FVvo0{1vngz zw}`0l+o6>@h^WxvaJ&_73jkQPYLykh+a?EurfC=&8p6oP2&9w?`ov-}jE|3}WIGSu zp5i-0l)$PLWrN6A+Ko~m>FlzoWbVJn*jh-B<} zarf?BSglqFAwWb3hr{rAJgBd)hsWcA-EPO~)vJ-4n~S!#wrM*9uwA`+_0LLve*Q)x zIt;*&cQ_nIWo0FfA3r{)&*$?Y7K@>yqXRaZ4Wpx@@OV6^tE+>@Gc!LH78XL&G<0@$ z!e+Cj9G{4m2_ZhUF!N3_x(ERXAuu*JHgEso!-pY+KwVuO)~s2BhK2^@=jSK1&B@6z zv!RKZcPe>#c_)bI`$oNOHX9y2dW0uWo?y$CEeWUJ@At#ya$*1e{nMLvHaa>waQ^&x z0GP37QUNnZ6}Q{{E)gv;DgXeiRx5gXdN43BfK8h=B_yzJ-8$svCf;6wKmav0H5eTo zh23sXXtMw;%8C^${$R!vn0&I>?RNC^^q{}LAC;ArDX-&}mKK~mc@kq|V{kYeFmt#- zL`#g#S>i$9a=Fmb(t_&hYCL`VG^xF&X=rL{!if_nFg`wRntU9J+wDGU+WR>;91es+ zA=vG9Y}l}2Uc1MSALG!WLy%G;Jw1I<38hdfh9{4=fG>%uHoFdbI8ohL|0cAjvhUVk&%&vw&msJuvjdH z$_Zxf(=;t4g2CX|092y_EEWs8y1FniF)^p_`t|ELd-g08MS<07#nPoq(bm?6BS((l z*|TReYzGGiaq;3s=(=unAE?1#@M{48Qp#&a1!QDoKvh*#S69y<)!f{S)2B~E2r(lo zyId}`x3{CJstSXHlSiHd0|PjC@E}5=5Yp08J(-_GoHH>qii?ZWGBYzD0!UmgCc((a z2r4QnaNxiJ^!4@O(xpovqIq9X!r?HAii)sn*Dl<;bqfz3Jb=^bG#d8Ha5%iWx3@Pk zeXLSZQSlohGF%rC(R4i%jYgp;O46l3M3|VEKsX$R)9FN7TAJZ(%*@qYU0t=nbEgtg z%0_1HH!6Uc;cz&h>pCnJ%Yy5enPIcpkdu=GyWMVve7}^kG2WgQpsMN^GoLgnAPz-Q z4A=8!z|1FARh@p5nh`M&2sANs(@SSz63pBb2n1&EoAa0Hy8deb4__Ip)bVhN+YBT; zUN0*vE4Es#?ces@Kpazd1eM+0-MzE>5-!^A?(SY)*Y`5>H!q)Faxn8Zx~}g{L>^e+ zimAN3yx3x~+#sUwy)=eNz|4;|P5Wsu7)(eWm}ds!2nK__nx^dl@Q)YHZUHd!Uo}nJ zk&HaBfB*mp27~>Yrfro{{^`ZhOAaaJ2eDY}wO}x~U|uqM(Y;?$l-~hxFNR({T(lv3^` zqBo}Q)Qlbue}&19J(`&L6IE3sW_Xwu@I3r}f3XmvoS9z-;3J}hRV_1r2|y*Ho4T$C fR8{?Kv9bRPOi)DC7R{-k00000NkvXXu0mjfwLkGH literal 1058 zcmV+-1l{|IP)oS;Cdrz!4Jf<~H<~4IpYNI@(BVAPb;i2{iPu0YptjM~lP;a`Q(a3Xi<} zm~k9ivQEQs@E3b#JTo3zEygw;kN=EDqrd%r-|O{y{$MaTl z5ZEpRaUBkafr^f?Feb)sx7$BfLBoW?$<4!lRt2mC&;y=n!)o)xiQK8cy>~nt;-OaC z9A6eIfD2ky$ICPtB(^H|iYv1sjg*s}PUlypQXr6#SA1t9;Dv8>TNiL&w)K)*yPoR` zFSV`D&gu0lclz(Fe9%AS*bI%J_Unk9{EN@u}lvk4xLM}UUEtJO+)tjS~&_`V;F zi9Vf9Nt_LA%45A=2k#9KXS12mc*o=MWvtun7C|LdCRBzqAU#)}3W)pt{_9wm%SAiH z7sHyuU6lt<1Y#CEDu^%cE{fH6I-Pc+ZAs>YP&&B%%OVrR6~^3-8%PWowxp z1)-?qU)dFy2eB$VG@aWxl7m!dUrYpYNfe?8JZOCFXJbmgpD4t4Q^f>$iJy?#aVi4Q zM0g26j-!xHh*aL3_uyB zLaj861E@kHb3$x!CWosuXE-2&9a{#14qK2KVq{2jq^{XwNJlQJUlN4Y4(Ng0i^ z&6yBI=cpX2HoCKttbUsvsyf4*WI-i>;Bx6BR;)d!q}#2(SS*wt&#FMwb<}$aYf=Q( z%g8^KVNKmK@FPo0cWOtG5>Xjg*A4F@wSy`T|){2&ZM1iXyV%Xo^^HgKQK cGBibQV{c?-a;OG>?f?J)07*qoM6N<$f-&vYDgXcg diff --git a/theme/default/assets/features/inventory/assets/inventory.png b/theme/default/assets/features/inventory/assets/inventory.png index bd29b8a14c511f130ba452ef64b8dc4c393fd09a..479aaf7bf91d59ddc9d8b0d6a30f3621cc5d27de 100644 GIT binary patch delta 2847 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 delta 3688 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 M07*qoM6N<$f*M`qjsO4v diff --git a/theme/default/assets/features/inventory/assets/unequipped.png b/theme/default/assets/features/inventory/assets/unequipped.png index 3428d3d5a41cfecb43bfa7c25d86012e64132622..e28b66734465d0dd797876c7239e95c140fe21e8 100644 GIT binary patch literal 1899 zcmV-x2bB1UP)CBbBUQzE?%$qdy1lL zBO;%a(oaM`1Rz$KF*Dy6LWG!kBAre@oS2x1l$)DMEugivRaI5>CqjsyGV_m#Xd8eH z7TuG~{1CuhX1hWppZUh~7U1*wzAuFMl89bibA=XQ z=5M5wdo)e^_v*3LlMTFHZ?zEO)BhLo06;{q3L!rAdcD=F$8ri#6lIWzeCs34Iz;4C z6lE~y{_;nty}i9l2=V#4Dzk}{@~u!P^w;e5(l5}`(&8kdftMr8Iz%+k($eD0UN05k za=DHW(d#cqmUW2eb(hO^Bzr9YpsA_J0pP=R5`c6%jfI5;l(7>2Fr)c-0rmCu2P(BN zD+CcC9*?7;p#g1eZAhh3Fbo4kWH-)^`uh3QQlHe)-w-5{lkxr-K za=9!&R?K|gFpM{pty{P5A);Rau=hKz42DD^0mCrha5!?tP18gokw732Kv!4Sim~SA zW^CWS9b;o-SXfwqq9~Sf5>bN?;tRpd2g(s|nkG`I6goRQ(c0RIWHOmk+`_^FjvhUV zLx&FKjnUE3f%E6j!|is%Fbu1yiJ1=w0G?8WXS+!%mBO)O$1pTBgiDt$!RPZ~etv%G zj~R_d(bd(3?1<0e5v8p2v)zP<(AU?8BS)UMWkpfY-`|hs=4QlVF~njq z?Ao;py}i8!+UzZRGd+Z8u}XFyMB((bLm|{rmUljL*!>;PBzY zsHv&J)vH&rWy_X=@dAMWZr;3!O`Gh$c33YrgK3%&LLe9n!teLzwP|WMX_E>2%`3g9n(Ko5Q|+`-<8#GrGIGarf?B)Ya9MD;@woQ5qW?RU&#HCCJ^(FAT$&VCJt%jY34It*ym{3m0(b&Yc(9 zUAuM-SFT*a#*G_G?l{X~=C2IHm=LCzzrrP*oMDPoG9O9L^aZ9UaBMzyMq> z7lg>!M<^9d*Y$4&07xl6D>s_c=|n6RLm&{q?Ck8){psmx^!4?Dne(nxOC_sW06;t* zzXRY&IRTj&H8nMuot?$WlP3|4MlnA>k5i{k;px+-w$}On6 zr&1|w-nh<|x=HHBsjST|Jb|s{g7nymc5(%76CnAx^T8W>LQeMnnFBPC^ zTAY~!l}er7{~tsw9Ay2s<$>2Nsy_Oj0gvSiK(I>O;_&hj_!tP&1~r%cm4$jozVFRw7nJZGBb z!F=L@B1cSZZEZWNs;d4%L_b(Fg#}>d$A)3Nt?T*=UpxxV>$2>bhQZUb1`8{R>4=eg~kj9DxP+ zMoRhHP$)EF)xq|s2Cc2F-`%ib!$-{g0TETC5N3V^;9@eF9G;w`@gWO6cYJKU48%n002ovPDHLkV1jJnrRD$t literal 1079 zcmV-71jze|P)?tS62VIFFw z=J>MM0bHOJE&P~H0|`~--f?Aiq?2~C-ERLfNeTou@`~^51bFGMx~U6rIcojI&93LV zf+wi;nOvo@{e1vW1d&XoGw|-dfF}}+br9hO_z0c|qBU2?7n{do6Pp5_H4qV~Hw8Qa z#9ib4JOdzZs)9FbDtH2jv%t6<%@I(QS zbr8Qf)^$u*O{df3@pvRfa=YE0*N~*U>IU9qGLeG!5?o^w z#D7JiI)Mk(go>6GfCyE$@m-!pC@%YlT{NH1m2K7WcuaJ}28d5P$PxPjxR(m5sM&tM zPjqFqTG@$>sq=n$%vpvtix{`88e_*>$rDlnh+R-5@ZV%Rwu&qk3ww!HD)7pvAFfe# zS2$Gn8xvEori$$nUl{kopVO7RAEZ_Q!tSWJ9PcWpbFXMuDKsmhj8j#NzOK7M70QtS zC?gG9VmtaEw}Dl5l8%6}Dc!Qjwa}}I*b3RC08%uEmks`_2nFNjHLH_3(y&Xm1ZIM* zV}UXkSS1{DfpK$lg4CranV1=AEmy$H?b70I?T|6E3ydpm z?Qo?#p@^AnrR0sa`=Vvbg_`es2HJK?US}1y`nqta2CDi3kt@d4K712ed@Hv2HvMRq zk)vPXgpz?1OQG3Zp}Aag)ax366Ee>_vk?M#Z&??OrMnL&$6G?}V`354(1UG!34=xUhL#z`mT_5}TVL+`LlYYYp zP=iBg!`M$VuFlTIWTb%7Ep$a#bW?9;ba!ELWdLG%E@EtN xZ)9Y7E@N_eaCC1jX>DO=WiC)oM=~@;Zewp`Wpbznf9?PP002ovPDHLkV1nku^TPlD diff --git a/theme/default/assets/features/inventory/assets/use.png b/theme/default/assets/features/inventory/assets/use.png index 7ad8ce67f18057d9facefef85738eb33d20cadde..910b68309c97f32d7730ed7f18bb0c2dd89ebca0 100644 GIT binary patch literal 1973 zcmV;m2TJ&fP)NE&vlD-!Dm1Et*+D=N}TeWAXf_#wDh5kxE-1AdW~W;F)a=1V>-73y4X#u!ZF zG}(#~%EBg1V`k!b=iKx3gGtsTlbJg&vHSaW=05*({tx%exz9c45;G%L{C@xUG)-GY zL;(Qe}iEMmioIo%btPnzc%*?+aqIKB}IvZww1mH6%WvgkLFEi0* zlt3U5_@xly_mfj3?=bUYrPMjYF#haR<0t{Y-@j7Rw97>FQBDpW!pt`;%Q_eihyRl< zHf;iazkj`^X`fHd@wWyu|I@bZoev&7=uH)uGJ!xK@V*e@HW4k(yF&*s^O#cVBf~Jh zoG)hH1bjZgvMq@G#tNHwp?0(oQjE?w3-&Pr+cYo`^aDP`-@6eEAYmN(2G{Y}>XC zTefV0$Ky$xn2Y=9wAeGsEeGAfi<|5v|N> z#7Qj6g3IN?xpU`GS67!+AETqAIC=6U#>dA|T3YJ(89_uV=M;09pp=4?6334p&)U3{ z5~og`!jmUYP*PHo@dGZW1jfe3uxHO6Y~Q}!u|_F1qkZ}EWps6QVe#VZUit)p%pZk# zy~caJUhLhw*HP@%t5>ma-#$d6(dpkiJ3Gsi%C%seOne3hy2#fukcXlOuf zZEec9uC6W|Ja`a;gM(PUd^zIrIHZ&qA0Nkw6DJUh#ZXjKluKdvl@1^b-~*=$DJ9Cv z%Fxu*lrrw&!-qI>C>ke7#M)ZRCF1co zHf`F3HEZUdbz@^=ICJI({TJHzpE^pslUV zNiCBrhH0AL2mnw@-FBjQ_39O>s;W>{Hve8%M@I)nM@OM)TAq9+XUekxfQgBTzXBLa zCvv;ph(sc|aNz=O-MWST{{A^Jy}i9CEG$f-&MQNcp1<8z*|1^5Z;0p*sZ>NXy$oua zhKh;`EL*k=)z#Igt*r$!K- zZP>OADJ7(o00582rRl8iSzx}_@2Bw%iBlvN5 zclYDj*OD$3-QC@fZQI_#%p(hLFEz|OV%zqPWafc1M@+S~wbi<=-yx#P1#_4JW_~86 z{F!N*NzDUk-pEYTd@QA`1MrW$yGsKzKa^6|r7{mVc%?K=b3jVDMJe^C{Mk#5QmQqP zNc_Y!&4F~WnI3fiO4GD60CL*NNWoL3)bB!}(C1DC8GmZv^?H{S6%`$1=8tpTl9_^; z{{`TW@p$~Ro}Ql3Ote{l?6Gm<#!{u!4kFq)d8W236FoD3jn^N0v@!Ed!!Y96_{che zHwgxV)k26`X8sUBfQWvOERLCn02oB{g>Bm*!!YKSvl;R~j!sjAp8IBV9Z9u9|aHk(N><+*jf58x$oy6!{Jaya4kJq0Ki_!Znq0ZqmkBRgLUMw*=)jh0#lz(r~1jnhQN6|9*0Hccs#QE z$sd8W4e2VFC;?QjvsksQu)3kw|LnS61VS2z)8kJ3fj~ z()=GtfCIQh&gV0&AC$89g`XqfXB6# zPeximx#|weG5>+CbYioCrzlcm6!|_ zr*(M}_sHLR;ApyD&UF$^+*^O{TwndTr~aCQa4;u)aI4k&7t}#w&77qE2IEo&tmJPo zFo($&@BaZ1A2V0IpT_3^001p?MObuGZ)S9NVRB^vVtFoNY;SL5WO*)Qa(QrcZ!T$V jVP|D7P)Z)9b1s0M%T00000NkvXXu0mjf5qt+H diff --git a/theme/default/assets/features/teams/assets/team.png b/theme/default/assets/features/teams/assets/team.png new file mode 100644 index 0000000000000000000000000000000000000000..183aa48baed52316b58dfbbb14c87eb893c9a109 GIT binary patch literal 29567 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 +
{{&teamMembers}}
- +
diff --git a/theme/default/assets/features/teams/templates/team-player-data.html b/theme/default/assets/features/teams/templates/team-player-data.html index f4e21ca0f..b3a60e778 100644 --- a/theme/default/assets/features/teams/templates/team-player-data.html +++ b/theme/default/assets/features/teams/templates/team-player-data.html @@ -2,9 +2,7 @@
{{playerName}}
-
- remove -
+ {{&teamRemove}}
{{&playerProperties}}
diff --git a/theme/default/assets/features/teams/templates/team-remove.html b/theme/default/assets/features/teams/templates/team-remove.html new file mode 100644 index 000000000..588361e0d --- /dev/null +++ b/theme/default/assets/features/teams/templates/team-remove.html @@ -0,0 +1,3 @@ +
+ remove +
diff --git a/theme/default/assets/features/teams/templates/ui-teams.html b/theme/default/assets/features/teams/templates/ui-teams.html index cfa22562c..8f1d21315 100644 --- a/theme/default/assets/features/teams/templates/ui-teams.html +++ b/theme/default/assets/features/teams/templates/ui-teams.html @@ -1,5 +1,9 @@ -
+
close -
+
{{title}}
+
+ {{content}} +
+
open diff --git a/theme/default/assets/icons/book.png b/theme/default/assets/icons/book.png index 3fd0ea9f4882a1b1c69d33f9a83970cd3b9ee402..39b820ba274224ed8d79d82691603dac7ed8847d 100644 GIT binary patch delta 3235 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 literal 5450 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 diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index 77fb14255..a7a7ff355 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -14,6 +14,11 @@ display: block; width: 90%; text-align: left; + cursor: pointer; +} + +.properties-list-container { + cursor: pointer; } .team-remove-container { From 45c5b60388b63b7c74ddbfc2e5d42ac324c8968e Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 20 Feb 2023 19:33:10 +0100 Subject: [PATCH 13/82] - Reldens - v4.0.0 - Teams UI. - New favicon. --- lib/game/client/game-engine.js | 5 +- lib/game/client/room-events.js | 5 +- lib/game/client/scene-preloader.js | 24 +++--- lib/game/client/ui-factory.js | 1 + lib/game/client/user-interface.js | 77 ++++++++++++++---- lib/game/constants.js | 1 + lib/teams/client/plugin.js | 18 +++- lib/teams/client/team-create-target-action.js | 8 +- lib/teams/client/team-message-handler.js | 25 +++--- lib/users/client/player-stats-ui.js | 1 + migrations/production/beta.26-sql-update.sql | 20 +++-- .../assets/favicon/android-chrome-192x192.png | Bin 13585 -> 42837 bytes .../assets/favicon/android-chrome-512x512.png | Bin 59651 -> 162608 bytes .../assets/favicon/apple-touch-icon.png | Bin 12201 -> 42475 bytes .../default/assets/favicon/favicon-16x16.png | Bin 531 -> 1634 bytes .../default/assets/favicon/favicon-32x32.png | Bin 1259 -> 3428 bytes .../features/teams/templates/ui-teams.html | 11 +-- theme/default/css/base.scss | 4 + theme/default/css/teams.scss | 32 +++++++- theme/default/favicon.ico | Bin 15406 -> 1150 bytes 20 files changed, 167 insertions(+), 65 deletions(-) diff --git a/lib/game/client/game-engine.js b/lib/game/client/game-engine.js index ec8234b4c..9a8fd85f0 100644 --- a/lib/game/client/game-engine.js +++ b/lib/game/client/game-engine.js @@ -44,18 +44,17 @@ class GameEngine extends Game { // get the window size: let {newWidth, newHeight} = this.getCurrentScreenSize(manager); - // @TODO - BETA - Make timeout 500 configurable. setTimeout(() => { this.eventsManager.emit('reldens.updateGameSizeBefore', this, newWidth, newHeight); this.scale.setGameSize(newWidth, newHeight); for(let key of Object.keys(this.uiScene.elementsUi)){ - let {uiX, uiY} = this.uiScene.getUiConfig(key, newWidth, newHeight); let uiElement = this.uiScene.elementsUi[key]; + let {uiX, uiY} = this.uiScene.getUiConfig(key, newWidth, newHeight); uiElement.x = uiX; uiElement.y = uiY; } this.eventsManager.emit('reldens.updateGameSizeAfter', this, newWidth, newHeight); - }, 500); + }, manager.config.get('client/general/gameEngine/updateGameSizeTimeOut', 500)); } getCurrentScreenSize(manager) diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index bae672194..77f17d3b8 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -27,6 +27,7 @@ class RoomEvents this.objectsUi = {}; this.tradeUi = {}; this.teamUi = {}; + this.currentTeam = false; } async activateRoom(room, previousScene = false) @@ -383,11 +384,11 @@ class RoomEvents initUi(props) { let uiScene = this.gameEngine.uiScene; - if(!uiScene || !sc.hasOwn(uiScene.userInterfaces, props.id)){ + if(!uiScene || !sc.hasOwn(uiScene.elementsUi, props.id)){ Logger.error('User interface not found on UI Scene: '+props.id); return false; } - let uiBox = uiScene.userInterfaces[props.id]; + let uiBox = uiScene.elementsUi[props.id]; this.uiSetTitle(uiBox, props); this.uiSetContent(uiBox, props, uiScene); let dialogContainer = uiBox.getChildByID('box-'+props.id); diff --git a/lib/game/client/scene-preloader.js b/lib/game/client/scene-preloader.js index 4fd1ea716..b933fd7ba 100644 --- a/lib/game/client/scene-preloader.js +++ b/lib/game/client/scene-preloader.js @@ -265,11 +265,7 @@ class ScenePreloader extends Scene getUiConfig(uiName, newWidth, newHeight) { let {uiX, uiY} = this.getUiPosition(uiName, newWidth, newHeight); - return { - enabled: this.gameManager.config.get('client/ui/'+uiName+'/enabled'), - uiX: uiX, - uiY: uiY - } + return {enabled: this.gameManager.config.get('client/ui/'+uiName+'/enabled'), uiX, uiY} } getUiPosition(uiName, newWidth, newHeight) @@ -277,19 +273,21 @@ class ScenePreloader extends Scene if('' === uiName){ uiName = 'default'; } - let uiX = this.gameManager.config.get('client/ui/'+uiName+'/x'); - let uiY = this.gameManager.config.get('client/ui/'+uiName+'/y'); + let uiConfig = this.gameManager.config.get('client/ui/'+uiName); + let uiX = sc.get(uiConfig, 'x', 0); + let uiY = sc.get(uiConfig, 'y', 0); if(this.gameManager.config.get('client/ui/screen/responsive')){ - let rX = this.gameManager.config.get('client/ui/'+uiName+'/responsiveX'); - let rY = this.gameManager.config.get('client/ui/'+uiName+'/responsiveY'); + let rX = sc.get(uiConfig, 'responsiveX', false); + let rY = sc.get(uiConfig, 'responsiveY', false); + let gameContainer = this.gameManager.gameDom.getElement('.game-container'); if(!newWidth){ - newWidth = this.gameManager.gameDom.getElement('.game-container').offsetWidth; + newWidth = gameContainer.offsetWidth; } if(!newHeight){ - newHeight = this.gameManager.gameDom.getElement('.game-container').offsetHeight; + newHeight = gameContainer.offsetHeight; } - uiX = rX ? rX * newWidth / 100 : 0; - uiY = rY ? rY * newHeight / 100 : 0; + uiX = false !== rX ? rX * newWidth / 100 : 0; + uiY = false !== rY ? rY * newHeight / 100 : 0; } return {uiX, uiY}; } diff --git a/lib/game/client/ui-factory.js b/lib/game/client/ui-factory.js index 0e917010e..e17c29100 100644 --- a/lib/game/client/ui-factory.js +++ b/lib/game/client/ui-factory.js @@ -17,6 +17,7 @@ class UiFactory create(uiCodeName, depth, defaultOpen, defaultClose, openCallback, closeCallback) { + // @TODO - BETA - Refactor with UserInterface. let {uiX, uiY} = this.uiScene.getUiConfig(uiCodeName); let newUiObject = this.uiScene.add.dom(uiX, uiY).createFromCache(uiCodeName); let openButton = newUiObject.getChildByProperty('id', uiCodeName+GameConst.UI_OPEN); diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index d25fe22fa..235613233 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -45,31 +45,80 @@ class UserInterface if(exists){ return sc.get(uiScene.userInterfaces, this.id, false); } + let dialogBox = this.createDialogBox(uiScene, templateKey); + this.createBoxContent(uiScene, templateKey, dialogBox); + let dialogContainer = gameDom.getElement('.ui-box.ui-dialog-box', dialogBox.node); + if(!dialogContainer){ + Logger.critical('Missing dialog container for template key: "'+templateKey+'".'); + return false; + } + dialogContainer.id = objectElementId; + dialogContainer.classList.add('type-'+(this.animProps?.type || 'dialog-box')); + let openButton = this.createOpenButton(dialogBox, dialogContainer, gameDom); + this.createCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom); + uiScene.userInterfaces[this.id] = this; + uiScene.elementsUi[this.id] = dialogBox; + return uiScene.userInterfaces[this.id]; + } + + createDialogBox(uiScene, templateKey) + { let {newWidth, newHeight} = uiScene.gameManager.gameEngine.getCurrentScreenSize(uiScene.gameManager); let {uiX, uiY} = uiScene.getUiPosition(this.uiPositionKey, newWidth, newHeight); - let dialogBox = uiScene.add.dom(uiX, uiY).createFromCache(templateKey); + return uiScene.add.dom(uiX, uiY).createFromCache(templateKey); + } + + createBoxContent(uiScene, templateKey, dialogBox) + { let messageTemplate = uiScene.cache.html.get(templateKey); dialogBox.innerHTML = uiScene.gameManager.gameEngine.parseTemplate(messageTemplate, { title: this.initialTitle, content: this.initialContent }); - let dialogContainer = dialogBox.getChildByProperty('className', 'ui-box ui-dialog-box'); - if(!dialogContainer){ - Logger.critical('Missing dialog container for template key: "'+templateKey+'".'); + } + + createOpenButton(dialogBox, dialogContainer, gameDom) + { + let openButton = gameDom.getElement('.'+GameConst.UI_BOX + GameConst.UI_OPEN, dialogBox.node); + if(!openButton){ return false; } - dialogContainer.id = objectElementId; - dialogContainer.classList.add('type-'+(this.animProps?.type || 'dialog-box')); - let boxClose = dialogBox.getChildByProperty('className', 'box-close'); - if(boxClose){ - boxClose.id = 'box-close-' + this.id; - boxClose.addEventListener('click', () => { - dialogContainer.style.display = 'none'; - uiScene.gameManager.activeRoomEvents.room.send('*', {act: GameConst.CLOSE_UI_ACTION, id: this.id}); - }); + openButton.id = GameConst.UI_BOX + GameConst.UI_OPEN + '-' + this.id + openButton.addEventListener('click', () => { + if(sc.get(this.animProps, 'defaultOpen', false)){ + dialogContainer.style.display = 'block'; + openButton.style.display = 'none'; + } + if(sc.isFunction(this.animProps['openCallBack'])){ + this.animProps['openCallBack'](); + } + }); + return openButton; + } + + createCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom) + { + let closeButton = gameDom.getElement('.'+GameConst.UI_BOX + GameConst.UI_CLOSE, dialogBox.node); + if(!closeButton){ + return false; } - uiScene.userInterfaces[this.id] = dialogBox; + closeButton.id = GameConst.UI_BOX + GameConst.UI_CLOSE + '-' + this.id; + closeButton.addEventListener('click', () => { + if(!sc.hasOwn(this.animProps, 'sendCloseMessage') || false === this.animProps['sendCloseMessage']){ + uiScene.gameManager.activeRoomEvents.room.send('*', {act: GameConst.CLOSE_UI_ACTION, id: this.id}); + } + if(sc.get(this.animProps, 'defaultClose', false)){ + dialogContainer.style.display = 'none'; + if(openButton){ + openButton.style.display = 'block'; + } + } + if (sc.isFunction(this.animProps['closeCallback'])) { + this.animProps['closeCallback'](); + } + }); } + } module.exports.UserInterface = UserInterface; diff --git a/lib/game/constants.js b/lib/game/constants.js index e7bd327c4..f3d45aaf3 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -24,6 +24,7 @@ module.exports.GameConst = { GAME_OVER: 'go', REVIVED: 'rv', BUTTON_OPTION: 'btn-opt', + UI_BOX: 'box', UI_CLOSE: '-close', UI_OPEN: '-open', // movement: diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 094b4dc50..4d3f3d175 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -15,7 +15,8 @@ const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface { - setup(props) { + setup(props) + { this.teamTargetActions = new TeamTargetActions(); this.gameManager = sc.get(props, 'gameManager', false); // this.clanUi = new UserInterface(this.gameManager, {id: TeamsConst.CLAN_KEY, type: TeamsConst.CLAN_KEY}); @@ -35,6 +36,21 @@ class TeamsPlugin extends PluginInterface this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } + fetchTeamPlayerBySessionId(sessionId) + { + let currentTeam = this.gameManager.activeRoomEvents.currentTeam; + if(!currentTeam){ + return false; + } + for(let i of Object.keys(currentTeam)){ + let teamPlayer = currentTeam[i]; + if(teamPlayer.sessionId === sessionId){ + return teamPlayer; + } + } + return false; + } + } module.exports.TeamsPlugin = TeamsPlugin; diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 21df4a864..591224d41 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -13,8 +13,8 @@ class TeamTargetActions showTeamInviteAction(gameManager, target, previousTarget, targetName) { - // @TODO - Return false if player is already in the current team. - if(GameConst.TYPE_PLAYER !== target.type || gameManager.getCurrentPlayer().playerId === target.id){ + let playerInTeam = gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id); + if(GameConst.TYPE_PLAYER !== target.type || playerInTeam){ return false; } let uiScene = gameManager.gameEngine.uiScene; @@ -33,9 +33,11 @@ class TeamTargetActions inviteLabel: gameManager.config.get('team/titles/inviteLabel', TeamsConst.LABELS.INVITE_BUTTON_LABEL) } ); - gameManager.gameDom.getElement('.team-invite-'+target.id+' button')?.addEventListener('click', () => { + let inviteButton = gameManager.gameDom.getElement('.team-invite-'+target.id+' button'); + inviteButton?.addEventListener('click', () => { let sendData = {act: TeamsConst.ACTIONS.TEAM_INVITE, id: target.id}; gameManager.room.send('*', sendData); + inviteButton.style.display = 'none'; }); } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index abe419fe9..af3b7e1eb 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -98,22 +98,24 @@ class TeamMessageHandler return false; } let players = sc.get(this.message, 'players', false); - console.log({message: this.message}); + // console.log({message: this.message}); + this.roomEvents.currentTeam = players; this.updateTeamBox(players, container); } createTeamUi(teamUiKey) { - let teamsUi = sc.hasOwn(this.roomEvents.teamUi, teamUiKey); - if(!teamsUi){ - this.roomEvents.teamUi[teamUiKey] = new UserInterface( - this.gameManager, - {id: teamUiKey, type: TeamsConst.KEY}, - 'assets/features/teams/templates/ui-teams.html', - TeamsConst.KEY - ); - this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); - } + let teamsUi = sc.get(this.roomEvents.teamUi, teamUiKey); + if(teamsUi){ + return teamsUi; + } + this.roomEvents.teamUi[teamUiKey] = new UserInterface( + this.gameManager, + {id: teamUiKey, type: TeamsConst.KEY, defaultOpen: true, defaultClose: true}, + 'assets/features/teams/templates/ui-teams.html', + TeamsConst.KEY + ); + this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); return this.roomEvents.teamUi[teamUiKey]; } @@ -227,7 +229,6 @@ class TeamMessageHandler let playerData = playersData[i]; let selectorPlayerName = '.team-player-'+i+' .player-name'; let selectorPlayerProperties = '.team-player-'+i+' .properties-list-container'; - console.log({i, selectorPlayerName, selectorPlayerProperties, playerData}); this.gameDom.getElement(selectorPlayerName).addEventListener('click', () => { this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); diff --git a/lib/users/client/player-stats-ui.js b/lib/users/client/player-stats-ui.js index f117cd8a9..1292015f7 100644 --- a/lib/users/client/player-stats-ui.js +++ b/lib/users/client/player-stats-ui.js @@ -22,6 +22,7 @@ class PlayerStatsUi scenePreloader.load.html('playerStat', 'assets/html/player-stat.html'); }); this.events.on('reldens.beforeCreateUiScene', (scenePreloader) => { + // @TODO - BETA - Replace by UiFactory. let statsUi = scenePreloader.getUiConfig('playerStats'); if(!statsUi.enabled){ return false; diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 37f59e4e4..d9eefc8e0 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -30,21 +30,23 @@ UPDATE `config` SET `type` = @boolean_id WHERE `type` = 'b'; UPDATE `config` SET `type` = @float_id WHERE `type` = 'i'; UPDATE `config` SET `type` = @json_id WHERE `type` = 'j'; UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; -INSERT INTO `config` VALUES (NULL, 'client', 'ui/options/acceptOrDecline', '{"1":{"label":"Accept","value":1},"2":{"label":"Decline","value":2}}', @json_id); -INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); -INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/leaderNameTitle', 'Team leader: %leaderName', @string_id); -INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; # Config: +INSERT INTO `config` VALUES (NULL, 'client', 'general/gameEngine/updateGameSizeTimeOut', '500', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/options/acceptOrDecline', '{"1":{"label":"Accept","value":1},"2":{"label":"Decline","value":2}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/leaderNameTitle', 'Team leader: %leaderName', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/enabled', '1', @boolean_id); -INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveX', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '5', @float_id); -INSERT INTO `config` VALUES (NULL, 'client', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveX', '100', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); diff --git a/theme/default/assets/favicon/android-chrome-192x192.png b/theme/default/assets/favicon/android-chrome-192x192.png index 4293e4cbfd12ea285701323a02cd8e87e13221a6..2a39620f1ffbf8082eaadadd0bfc1a592e0fbda9 100644 GIT binary patch literal 42837 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 ZqheIIO%EXY>x=??6l7GTp^{%i{sUCp5Yhku literal 13585 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

` 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

    dW*J=l~=~&X~%cL&eMkWiyQD* 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 z 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 JPDHLkV1l>xsYU<* literal 59651 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 diff --git a/theme/default/assets/favicon/apple-touch-icon.png b/theme/default/assets/favicon/apple-touch-icon.png index 14aea5c6fceed86642b2636d73a70aa02509d9ae..3e15f3aebb4f2a855b01839f5511d5e1e44db832 100644 GIT binary patch literal 42475 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

    Wf~&Mg+z(+sRf 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 diff --git a/theme/default/assets/favicon/favicon-16x16.png b/theme/default/assets/favicon/favicon-16x16.png index d61a69dc7e5e3073cfbc6e835991f564bc4d9606..2201712cfbabcd2bb101bfb5bf9fe1066d6a5c33 100644 GIT binary patch literal 1634 zcmZ{iX;4#F6vrViR@AoEtuCKB(^^~l;&i6enaWxFX{?JN?*SjNHtu`($ZrZeIE>}~9)jhjFot&1#tXsuLkF;WeLO!2Hqct`* zS(*us-bZ80Q?m1O%DaSpcOd5g>cP4B`AJDhY&N?CK`Uxq!p2n`*G{@FN;mW|ZR^<0 zd+AjRifd698fteFrs|rktSlIY+d9z#v0^q;n3e~nG5A?bemGne~*+T6_C0;|Jisk0gkhNUZqWaTvwtSi>m8tdDI z@>0G;v1#j$o44;=xOCYUiWW*$h=+hnN~R_zwzjsESvoU$B{_LKS*gBMU#BQHcK7s$ zdir+lJ+N-WaJ08yDwWncItuIqEK^S^ozLL#gt8KeqEuE~8kyhkCVVSa4-IV`?&H(Hx8_#!6{Lx*adm!K}GgZhZCMGz?75k|F7w4|2(v63R z&skLPUdZT*0FXqBS={yUMQGk;XQW}shJb;jx zADu_Mdhq1ol?OjQd-U>$r$4{=>6gd3saa`PQfb*4j9GKi>9cb(_ix;><;up*ySDAy zv~%m;;oaMF8BXg(YeTEO+1YM$Ioj%58t-4bc)+kJ_wCrN@f+XYDM#|YK6I)6 zh-J6>VC2!rSme>wDQD2XmM&kLA$Fbd{k~@F3Fj%-+4abuD~5I}f@5d=f2`kocK)le zvm3rzJh5ci%9**U{^!4~9zJT>uey97{PNB6wcAAuf5OoXg<&Bq7 zEz5}No1&Fr42hD#0JDMgV?Y9lrLj@|c+Gi=$d3^oB#Oak48wp_B87w!NGOFGASDdR zVVR^*B7r56Uxyd@{*&MjdPuzQ{}TqUeshgVFvKcEf_StS31h(IM+nS}5F|Q}sSqhe z#X=QRDuHA|iChRtnKnql^hTp0SS;@C?G=$Rnlg$O1;fPM6)z}3kQGyG@b>uKFsuur zJ$@_@g<&g&MgVkl;;i@`=50+F_F^RAjRHs|O#$KM6q7kg=O3n#^hCWeFP~ajL<*G$HH!WI}A_PT$g4D){K%yzi7D7ZCQW*Bj`PuwR z=lN#d_~PDh&}OoR&gNe3J?C@I{ho6L;PM|P{szzkA$FY};eXS_P~9|J0*ufB!hM&p zzkek8e7rG$9taaWdJ#MI0V_VoYI43YK=8-~a&wa)1ZK|w=~wp}1GJyGO8m(U71VL^ z8nLPC{|@lAcVV>dCI9ZB3NpI~lnz#WwgysH#J1yC$iJUL^ZF@%nL+42C1xUeYiocM zF0dzp5!#Qll7A-qW`ecSiVBiXveOGJFHTb~ELB0(3WAYAT*t<-vXpaQDSmssDO+=A zFWwzH(fuuGO@2}@@2LAKAQTxyGq&M6B^)cqn!Su$DXA_9PZJ&^fTy_?O*g2l7D!Fs z-3(YyC!MA0*|+zVkO+-ZW|5Y|=UI9@@(a4b>WXL{I(>&g;59qJbtxBOYT~6PzgrDJ z&c1wS_}FFg^N$q3>^?*M=}nwshDtH>=L)tTJcTc~4}V9GD)sY&VdcF0>=?r1#fr_U zvHr0Tt?hz5Y3m;*5gk>lG`U|tm!CfIY?N@{5Xl#}tAL;2__6_*8cA0G00003iGo7 diff --git a/theme/default/assets/favicon/favicon-32x32.png b/theme/default/assets/favicon/favicon-32x32.png index bca5c0e9d897b2e1e1cc3e2adea70dd07c014ff4..eaca46a74d9793b23c319371d280a3a3a92a7d2f 100644 GIT binary patch literal 3428 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 - close +

    + close
    {{title}}
    -
    - {{content}} -
    -
    +
    {{content}}
    -open +open diff --git a/theme/default/css/base.scss b/theme/default/css/base.scss index e76031ecd..cd4eb3990 100644 --- a/theme/default/css/base.scss +++ b/theme/default/css/base.scss @@ -407,6 +407,10 @@ form { cursor: pointer; } +.box-open { + cursor: pointer; +} + .box-content { min-height: 40px; max-height: 120px; diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index a7a7ff355..5b6387b92 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -1,3 +1,33 @@ +/** + * + * Reldens - Styles - Teams + * + */ + + +.teams-dialog-box { + min-width: 160px; + top: 200px; + right: 360px; +} + +@media (max-height: 400px) { + + .teams-dialog-box { + top: 50px; + } + +} + +.teams-open { + position: relative; + top: 230px; + right: 60px; + background: rgba(0, 0, 0, 0.5); + padding: 10px; + max-width: 40px; +} + .team-player, .property-box, .properties-list-container { @@ -57,4 +87,4 @@ .team-disband-action { float: right; -} \ No newline at end of file +} diff --git a/theme/default/favicon.ico b/theme/default/favicon.ico index 094cf7a57b3d9e56737e29c08b80cc6f58577638..0c77ba35d13e595b8f8794463ef0b31e801e40d7 100644 GIT binary patch literal 1150 zcmbW0$xqu?5XXOzAf(VV#OxS5Hs1R41~#?>HW)C*wT%PD6dNyLGuW806GS0PNt9N! zN>H{&qE##PP}EzG?XkC3?YYaL|I4S(PJ?pkrSIwc=FN=0Gfy+~B&ka5`g%#68foRC zBsEKt^qzn)3CnA{5RxQ(in`aR=;BZ|UM6I3;jC!ohYdSF@7VaUBy%=r;ENd@CkZV_ zQ8llowfwXu^Y`5@j<$;oBsXvmMQ~^pB;0L$Td?wM#>l5}4JT82?nFB|ozb&7a*Zy1 zJK@3WgnByJ68g;|!WXnFiT27|cUb7P8%a+mn9rtoc>j<` z5AKmn+@MrgVm6m$xwyjIbc~~fiM@yxv#K6hr^e^9(eLS|zt_d#{w{09MV@^22``^L z;qJj6E|-f77cOG6TFK>d%#RP?lUtBGm5lbgm>BIRGU&n6ZRTKaTVRWCzW$OIPakt< z|29si6H&SfLZJ{R#}A1Ox(Iq5!~(s{PKD_gZ}4=>oPPeCzyA20-+ujt+goe&IV}{I z7V!DJ)YR0lT3jONbK;iOjQO1mxh*12H?a{fjvhC|g9DU`1#XN*F`>)9J`B%#)4{ zah$Son%1)yRB;ee@pw|hR~a22zON!5>t$;x&C22|W6>zBt*z&MP$(44q*4?!y*&T` literal 15406 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 From 43fdce4d9e94ca47620475afe40b0d8661b6e791 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sat, 25 Feb 2023 20:24:10 +0100 Subject: [PATCH 14/82] - Reldens - v4.0.0 - Teams leave action. --- lib/game/client/ui-factory.js | 2 +- lib/game/client/user-interface.js | 27 +++++++---- lib/inventory/client/inventory-ui.js | 1 + lib/inventory/client/plugin.js | 8 +++- lib/inventory/client/trade-message-handler.js | 32 ++++++++++--- lib/teams/client/team-create-target-action.js | 2 +- lib/teams/client/team-message-handler.js | 39 ++++++++++----- lib/teams/client/templates-handler.js | 1 + lib/teams/constants.js | 4 +- lib/teams/server/message-actions.js | 3 +- lib/teams/server/message-actions/team-join.js | 2 +- .../server/message-actions/team-leave.js | 47 +++++++++++++++++++ lib/teams/server/team.js | 2 + .../teams/templates/team-container.html | 2 +- 14 files changed, 136 insertions(+), 36 deletions(-) create mode 100644 lib/teams/server/message-actions/team-leave.js diff --git a/lib/game/client/ui-factory.js b/lib/game/client/ui-factory.js index e17c29100..ff4ac3314 100644 --- a/lib/game/client/ui-factory.js +++ b/lib/game/client/ui-factory.js @@ -17,7 +17,7 @@ class UiFactory create(uiCodeName, depth, defaultOpen, defaultClose, openCallback, closeCallback) { - // @TODO - BETA - Refactor with UserInterface. + // @TODO - BETA - Remove and replace by UserInterface. let {uiX, uiY} = this.uiScene.getUiConfig(uiCodeName); let newUiObject = this.uiScene.add.dom(uiX, uiY).createFromCache(uiCodeName); let openButton = newUiObject.getChildByProperty('id', uiCodeName+GameConst.UI_OPEN); diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index 235613233..ea94271be 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -31,6 +31,9 @@ class UserInterface preloadUiElement(preloadScene) { + if(!this.template){ + return; + } preloadScene.load.html(this.id, this.template); } @@ -41,11 +44,7 @@ class UserInterface } let objectElementId = 'box-'+this.id; let gameDom = uiScene.gameManager.gameDom; - let exists = gameDom.getElement('#'+objectElementId); - if(exists){ - return sc.get(uiScene.userInterfaces, this.id, false); - } - let dialogBox = this.createDialogBox(uiScene, templateKey); + let dialogBox = sc.get(uiScene.elementsUi, this.id, this.createDialogBox(uiScene, templateKey)) this.createBoxContent(uiScene, templateKey, dialogBox); let dialogContainer = gameDom.getElement('.ui-box.ui-dialog-box', dialogBox.node); if(!dialogContainer){ @@ -54,11 +53,11 @@ class UserInterface } dialogContainer.id = objectElementId; dialogContainer.classList.add('type-'+(this.animProps?.type || 'dialog-box')); - let openButton = this.createOpenButton(dialogBox, dialogContainer, gameDom); - this.createCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom); + let openButton = this.activateOpenButton(dialogBox, dialogContainer, gameDom); + this.activateCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom); uiScene.userInterfaces[this.id] = this; uiScene.elementsUi[this.id] = dialogBox; - return uiScene.userInterfaces[this.id]; + return this; } createDialogBox(uiScene, templateKey) @@ -77,8 +76,9 @@ class UserInterface }); } - createOpenButton(dialogBox, dialogContainer, gameDom) + activateOpenButton(dialogBox, dialogContainer, gameDom) { + // @TODO - BETA - Extract into a new service. let openButton = gameDom.getElement('.'+GameConst.UI_BOX + GameConst.UI_OPEN, dialogBox.node); if(!openButton){ return false; @@ -88,6 +88,9 @@ class UserInterface if(sc.get(this.animProps, 'defaultOpen', false)){ dialogContainer.style.display = 'block'; openButton.style.display = 'none'; + if(false !== sc.get(this.animProps, 'depth', false)){ + dialogBox.setDepth(this.animProps.depth); + } } if(sc.isFunction(this.animProps['openCallBack'])){ this.animProps['openCallBack'](); @@ -96,8 +99,9 @@ class UserInterface return openButton; } - createCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom) + activateCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom) { + // @TODO - BETA - Extract into a new service. let closeButton = gameDom.getElement('.'+GameConst.UI_BOX + GameConst.UI_CLOSE, dialogBox.node); if(!closeButton){ return false; @@ -112,6 +116,9 @@ class UserInterface if(openButton){ openButton.style.display = 'block'; } + if(false !== sc.get(this.animProps, 'depth', false)){ + dialogBox.setDepth(1); + } } if (sc.isFunction(this.animProps['closeCallback'])) { this.animProps['closeCallback'](); diff --git a/lib/inventory/client/inventory-ui.js b/lib/inventory/client/inventory-ui.js index 25637d3c7..216c1c527 100644 --- a/lib/inventory/client/inventory-ui.js +++ b/lib/inventory/client/inventory-ui.js @@ -12,6 +12,7 @@ class InventoryUi extends UiFactory createUi() { + // @TODO - BETA - Remove and replace by UserInterface. this.create('inventory', 5, true, true, null, () => { this.inventoryVisibility('inventory'); }); diff --git a/lib/inventory/client/plugin.js b/lib/inventory/client/plugin.js index 32363d3d7..fba00eb94 100644 --- a/lib/inventory/client/plugin.js +++ b/lib/inventory/client/plugin.js @@ -56,12 +56,16 @@ class InventoryPlugin extends PluginInterface 'id', InventoryConst.INVENTORY_ITEMS ); + if(!inventoryPanel){ + Logger.error('Inventory UI not found.', inventoryPanel); + return false; + } let equipmentPanel = preloadScene.getUiElement('equipment').getChildByProperty( 'id', InventoryConst.EQUIPMENT_ITEMS ); - if(!inventoryPanel || !equipmentPanel){ - Logger.error(['Inventory/Equipment UI not found.', inventoryPanel, equipmentPanel]); + if(!equipmentPanel){ + Logger.error('Equipment UI not found.', equipmentPanel); return false; } let manager = preloadScene.gameManager.inventory.manager; diff --git a/lib/inventory/client/trade-message-handler.js b/lib/inventory/client/trade-message-handler.js index 49d943425..da5b1232c 100644 --- a/lib/inventory/client/trade-message-handler.js +++ b/lib/inventory/client/trade-message-handler.js @@ -8,6 +8,7 @@ const { ErrorManager, Logger, sc } = require('@reldens/utils'); const { InventoryConst } = require('../constants'); const { ObjectsConst } = require('../../objects/constants'); const { UserInterface } = require('../../game/client/user-interface'); +const {TeamsConst} = require("../../teams/constants"); class TradeMessageHandler { @@ -318,13 +319,28 @@ class TradeMessageHandler let functionLabels = ObjectsConst.TRADE_ACTIONS_FUNCTION_NAME; let templateParams = { tradeActionKey: this.message.id, - confirmLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/confirmLabel', functionLabels.CONFIRM), - disconfirmLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/disconfirmLabel', functionLabels.DISCONFIRM), - cancelLabel: this.gameManager.config.getWithoutLogs('client/trade/titles/cancelLabel', functionLabels.CANCEL), + confirmLabel: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/confirmLabel', + functionLabels.CONFIRM + ), + disconfirmLabel: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/disconfirmLabel', + functionLabels.DISCONFIRM + ), + cancelLabel: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/cancelLabel', + functionLabels.CANCEL + ), myItems: tradeItems, myItemsTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/myItems', 'My Items:'), - pushedToTradeTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/pushedToTradeTitle', 'Sending:'), - gotFromTradeTitle: this.gameManager.config.getWithoutLogs('client/trade/titles/gotFromTradeTitle', 'Receiving:'), + pushedToTradeTitle: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/pushedToTradeTitle', + 'Sending:' + ), + gotFromTradeTitle: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/gotFromTradeTitle', + 'Receiving:' + ), playerConfirmedLabel: this.playerConfirmedLabel(), }; return this.gameManager.gameEngine.parseTemplate(messageTemplate, templateParams); @@ -335,8 +351,10 @@ class TradeMessageHandler if(!this.message.playerConfirmed){ return ''; } - // @TODO - BETA - Change all the fixed concatenated names by .replace('%name', nameVariable). - return this.message.with + this.gameManager.config.getWithoutLogs('client/trade/titles/playerConfirmedLabel', ' CONFIRMED'); + return this.gameManager.config.getWithoutLogs( + 'client/trade/titles/playerConfirmedLabel', + '%playerName CONFIRMED' + ).replace('%playerName', this.message.with); } createTradeItemBox(item, exchangeDataItem) diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 591224d41..e7a13d284 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -43,4 +43,4 @@ class TeamTargetActions } -module.exports.TeamTargetActions = TeamTargetActions; \ No newline at end of file +module.exports.TeamTargetActions = TeamTargetActions; diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index af3b7e1eb..d7b455ddd 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -39,6 +39,9 @@ class TeamMessageHandler updateContents() { + if(-1 === this.message.act.indexOf(TeamsConst.TEAM_PREF)){ + return false; + } if(TeamsConst.ACTIONS.TEAM_INVITE === this.message.act){ return this.showTeamRequest(); } @@ -48,11 +51,14 @@ class TeamMessageHandler } return this.showTeamBox(); } + if(TeamsConst.ACTIONS.TEAM_LEFT === this.message.act){ + this.removeTeamUi(); + } } showTeamRequest() { - let teamsUi = this.createTeamUi(this.teamUiKey()); + this.createTeamUi(this.teamUiKey()); this.roomEvents.initUi({ id: this.teamUiKey(), title: this.gameManager.config.getWithoutLogs( @@ -60,17 +66,26 @@ class TeamMessageHandler TeamsConst.LABELS.TEAM_REQUEST_FROM ), content: this.message.from, - options: this.gameManager.config.getWithoutLogs('client/ui/options/acceptOrDecline'), - overrideSendOptions: { - act: TeamsConst.ACTIONS.TEAM_ACCEPTED, - id: this.message.id - } + options: this.gameManager.config.get('client/ui/options/acceptOrDecline'), + overrideSendOptions: {act: TeamsConst.ACTIONS.TEAM_ACCEPTED, id: this.message.id} }); - if(teamsUi){ - this.gameDom.getElement('#opt-2-'+this.teamUiKey())?.addEventListener('click', () => { - this.gameDom.getElement('#box-close-'+this.teamUiKey())?.click(); - }); + this.gameDom.getElement('#opt-2-'+this.teamUiKey())?.addEventListener('click', () => { + this.removeTeamUi(); + }); + } + + removeTeamUi() + { + let uiElement = this.gameManager.getUiElement(this.teamUiKey()); + if(!uiElement){ + Logger.error('UI Element not found by team UI key "'+this.teamUiKey()+'".'); + return false; } + uiElement.removeElement(); + delete this.uiScene.userInterfaces[this.teamUiKey()]; + delete this.uiScene.elementsUi[this.teamUiKey()]; + delete this.roomEvents.teamUi; + this.gameManager.activeRoomEvents.currentTeam = false; } teamUiKey() @@ -98,7 +113,6 @@ class TeamMessageHandler return false; } let players = sc.get(this.message, 'players', false); - // console.log({message: this.message}); this.roomEvents.currentTeam = players; this.updateTeamBox(players, container); } @@ -109,6 +123,9 @@ class TeamMessageHandler if(teamsUi){ return teamsUi; } + if(!this.roomEvents.teamUi){ + this.roomEvents.teamUi = {}; + } this.roomEvents.teamUi[teamUiKey] = new UserInterface( this.gameManager, {id: teamUiKey, type: TeamsConst.KEY, defaultOpen: true, defaultClose: true}, diff --git a/lib/teams/client/templates-handler.js b/lib/teams/client/templates-handler.js index 42f98bc67..886f2c052 100644 --- a/lib/teams/client/templates-handler.js +++ b/lib/teams/client/templates-handler.js @@ -16,6 +16,7 @@ class TemplatesHandler preloadScene.load.html(TeamsConst.CLAN_KEY, teamsTemplatePath+'ui-clan.html'); preloadScene.load.html('teamPlayerInvite', teamsTemplatePath+'team-invite.html'); preloadScene.load.html('teamPlayerAccept', teamsTemplatePath+'team-accept.html'); + preloadScene.load.html('teamRemove', teamsTemplatePath+'team-remove.html'); preloadScene.load.html('teamContainer', teamsTemplatePath+'team-container.html'); preloadScene.load.html('teamPlayerData', teamsTemplatePath+'team-player-data.html'); preloadScene.load.html('teamsSharedProperty', teamsTemplatePath+'shared-property.html'); diff --git a/lib/teams/constants.js b/lib/teams/constants.js index c5ce705e2..5a9833d85 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -14,7 +14,9 @@ module.exports.TeamsConst = { TEAM_INVITE: pref+'inv', TEAM_ACCEPTED: pref+'acp', TEAM_LEAVE: pref+'lev', - TEAM_UPDATE: pref+'upd' + TEAM_UPDATE: pref+'upd', + TEAM_LEFT: pref+'lef', + TEAM_REMOVE: pref+'rem', }, LABELS: { INVITE_BUTTON_LABEL: 'Team - Invite', diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/message-actions.js index 51c9309e3..aa6349bb0 100644 --- a/lib/teams/server/message-actions.js +++ b/lib/teams/server/message-actions.js @@ -6,6 +6,7 @@ const { TryTeamStart } = require('./message-actions/try-team-start'); const { TeamJoin } = require('./message-actions/team-join'); +const { TeamLeave } = require('./message-actions/team-leave'); const { TeamsConst } = require('../constants'); const { sc } = require('@reldens/utils'); @@ -34,7 +35,7 @@ class TeamsMessageActions return true; } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - this.teamsPlugin.teams[playerSchema.currentTeam].leave(playerSchema); + TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); return true; } } diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index 2ba2d2800..ab2a2432f 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -1,6 +1,6 @@ /** * - * Reldens - TeamsMessageActions + * Reldens - TeamJoin * */ diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js new file mode 100644 index 000000000..a58b546ca --- /dev/null +++ b/lib/teams/server/message-actions/team-leave.js @@ -0,0 +1,47 @@ +/** + * + * Reldens - TeamLeave + * + */ + +const { TeamUpdatesHandler } = require('../team-updates-handler'); +const { TeamsConst } = require('../../constants'); +const { Logger } = require('@reldens/utils'); + +class TeamLeave +{ + + static execute(client, data, room, playerSchema, teamsPlugin) + { + let teamId = playerSchema.currentTeam; + if(!teamId){ + return false; + } + let currentTeam = teamsPlugin.teams[teamId]; + if(!currentTeam){ + Logger.error('Player "'+playerSchema.player_id+'"current team "'+teamId+'"not found.'); + playerSchema.currentTeam = false; + return false; + } + let playerIds = Object.keys(currentTeam.players); + let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [playerSchema.id]; + for(let playerId of removeByKeys){ + let sendUpdate = { + act: TeamsConst.ACTIONS.TEAM_LEFT, + id: currentTeam.ownerClient.id, + listener: TeamsConst.KEY + }; + currentTeam.clients[playerId].send('*', sendUpdate); + currentTeam.leave(currentTeam.players[playerId]); + } + if(0 === Object.keys(currentTeam.players).length){ + delete teamsPlugin.teams[teamId]; + return true; + } + TeamUpdatesHandler.updateTeamPlayers(currentTeam); + return true; + } + +} + +module.exports.TeamLeave = TeamLeave; diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index fd1c2e06d..ff467a1de 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -5,6 +5,7 @@ */ const { ErrorManager, sc } = require('@reldens/utils'); +const {TeamsConst} = require("../constants"); class Team { @@ -39,6 +40,7 @@ class Team leave(playerSchema) { this.revertModifiers(playerSchema); + playerSchema.currentTeam = false; delete this.clients[playerSchema.id]; delete this.players[playerSchema.id]; } diff --git a/theme/default/assets/features/teams/templates/team-container.html b/theme/default/assets/features/teams/templates/team-container.html index b99ed336c..bc9ad1fa0 100644 --- a/theme/default/assets/features/teams/templates/team-container.html +++ b/theme/default/assets/features/teams/templates/team-container.html @@ -4,7 +4,7 @@

- +
From f3500eab49174e7954f2ec9e7c3408f942edc594 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 26 Feb 2023 20:58:57 +0100 Subject: [PATCH 15/82] - Reldens - v4.0.0 - Touch events fix. - Release movement fix. --- lib/actions/client/preloader-handler.js | 33 ++++++++++--------- lib/game/client/game-dom.js | 2 +- lib/game/client/scene-dynamic.js | 29 +++++++++------- lib/game/client/scene-preloader.js | 10 +----- lib/game/constants.js | 1 + lib/inventory/client/trade-message-handler.js | 1 - lib/objects/client/animation-engine.js | 3 +- lib/teams/server/team.js | 1 - lib/users/client/player-engine.js | 2 +- migrations/production/beta.26-sql-update.sql | 1 + 10 files changed, 42 insertions(+), 41 deletions(-) diff --git a/lib/actions/client/preloader-handler.js b/lib/actions/client/preloader-handler.js index 6cd36ddee..83ad3f145 100644 --- a/lib/actions/client/preloader-handler.js +++ b/lib/actions/client/preloader-handler.js @@ -5,6 +5,7 @@ */ const { Logger, sc } = require('@reldens/utils'); +const { GameConst } = require('../../game/constants'); class PreloaderHandler { @@ -144,27 +145,29 @@ class PreloaderHandler { if(sc.hasOwn(data.animationData, ['type', 'img']) && data.animationData.type === 'spritesheet'){ let animDir = sc.get(data.animationData, 'dir', 0); - if(0 < animDir){ - // @TODO - BETA - Refactor and implement animDir = 1 (both): up_right, up_left, down_right, - // down_left. - uiScene.directionalAnimations[this.getAnimationKey(data)] = data.animationData.dir; - if(animDir === 1 || animDir === 2){ - this.createWithDirection(data, uiScene, 'up'); - this.createWithDirection(data, uiScene, 'down'); - } - if(animDir === 1 || animDir === 3){ - this.createWithDirection(data, uiScene, 'left'); - this.createWithDirection(data, uiScene, 'right'); - } - } else { - this.createWithDirection(data, uiScene); - } + 0 < animDir + ? this.createWithMultipleDirections(uiScene, data, animDir) + : this.createWithDirection(data, uiScene); } if(data.classKey && sc.isObjectFunction(data.classKey, 'createAnimation')){ data.classKey.createAnimation({data, uiScene, pack: this}); } } + createWithMultipleDirections(uiScene, data, animDir) { + // @TODO - BETA - Refactor and implement animDir = 1 (both): up_right, up_left, down_right, + // down_left. + uiScene.directionalAnimations[this.getAnimationKey(data)] = data.animationData.dir; + if(1 === animDir || 2 === animDir){ + this.createWithDirection(data, uiScene, GameConst.UP); + this.createWithDirection(data, uiScene, GameConst.DOWN); + } + if(1 === animDir || 3 === animDir){ + this.createWithDirection(data, uiScene, GameConst.LEFT); + this.createWithDirection(data, uiScene, GameConst.RIGHT); + } + } + createWithDirection(data, uiScene, direction = false) { let animationCreateData = this.prepareAnimationData(data, uiScene, direction); diff --git a/lib/game/client/game-dom.js b/lib/game/client/game-dom.js index e05d0bd77..5bcaa5711 100644 --- a/lib/game/client/game-dom.js +++ b/lib/game/client/game-dom.js @@ -68,7 +68,7 @@ class GameDom insideInput() { - return (this.activeElement().tagName.toLowerCase() === 'input'); + return 'input' === this.activeElement().tagName.toLowerCase(); } getJSON(url, callback) diff --git a/lib/game/client/scene-dynamic.js b/lib/game/client/scene-dynamic.js index b30ebf0e5..37aa45109 100644 --- a/lib/game/client/scene-dynamic.js +++ b/lib/game/client/scene-dynamic.js @@ -189,29 +189,34 @@ class SceneDynamic extends Scene executePointerDownAction(pointer, currentlyOver) { + if(0 < currentlyOver.length){ + return false; + } + if(!this.gameManager.config.get('client/players/tapMovement/enabled')){ + return false; + } + if(this.gameManager.activeRoomEvents.sceneData?.worldConfig?.applyGravity){ + return false; + } let primaryMove = this.gameManager.config.get('client/ui/controls/primaryMove'); - if((!pointer.primaryDown && primaryMove) || (pointer.primaryDown && !primaryMove)){ + let primaryTouch = this.gameManager.config.get('client/ui/controls/allowPrimaryTouch'); + if( + (!pointer.wasTouch && !pointer.primaryDown && primaryMove) + || (!pointer.wasTouch && pointer.primaryDown && !primaryMove) + || (pointer.wasTouch && !pointer.primaryDown && primaryTouch) + ){ return false; } // @TODO - BETA - Temporal avoid double actions, if you target something you will not be moved to the - // pointer, in a future release this will be configurable so you can walk to objects and they get - // activated, for example, click on and NPC, automatically walk close and automatically get a dialog + // pointer, in a future release this will be configurable, so you can walk to objects and they get + // activated, for example: click on and NPC, automatically walk close and automatically get a dialog // opened. if(this.gameManager.gameDom.insideInput()){ this.gameManager.gameDom.activeElement().blur(); } - if(currentlyOver.length){ - return false; - } if(!this.appendRowAndColumn(pointer)){ return false; } - if( - !this.gameManager.config.get('client/players/tapMovement/enabled') - || this.gameManager.activeRoomEvents.sceneData?.worldConfig?.applyGravity - ){ - return false; - } this.player.moveToPointer(pointer); this.updatePointerObject(pointer); } diff --git a/lib/game/client/scene-preloader.js b/lib/game/client/scene-preloader.js index b933fd7ba..050dbdedf 100644 --- a/lib/game/client/scene-preloader.js +++ b/lib/game/client/scene-preloader.js @@ -463,7 +463,7 @@ class ScenePreloader extends Scene type: action.type }; } - this.repeatHold(dataSend); + this.gameManager.activeRoomEvents.room.send('*', dataSend); } endHold(event, button) @@ -476,14 +476,6 @@ class ScenePreloader extends Scene this.gameManager.activeRoomEvents.room.send('*', {act: GameConst.STOP}); } - repeatHold(sendData) - { - this.gameManager.activeRoomEvents.room.send('*', sendData); - this.holdTimer = setTimeout(() => { - this.repeatHold(sendData); - }, (this.timeout || 0)); - } - createProgressBar() { let Rectangle = Geom.Rectangle; diff --git a/lib/game/constants.js b/lib/game/constants.js index f3d45aaf3..0054cfe1c 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -27,6 +27,7 @@ module.exports.GameConst = { UI_BOX: 'box', UI_CLOSE: '-close', UI_OPEN: '-open', + CANVAS: 'CANVAS', // movement: UP: 'up', LEFT: 'left', diff --git a/lib/inventory/client/trade-message-handler.js b/lib/inventory/client/trade-message-handler.js index da5b1232c..fad7eab98 100644 --- a/lib/inventory/client/trade-message-handler.js +++ b/lib/inventory/client/trade-message-handler.js @@ -8,7 +8,6 @@ const { ErrorManager, Logger, sc } = require('@reldens/utils'); const { InventoryConst } = require('../constants'); const { ObjectsConst } = require('../../objects/constants'); const { UserInterface } = require('../../game/client/user-interface'); -const {TeamsConst} = require("../../teams/constants"); class TradeMessageHandler { diff --git a/lib/objects/client/animation-engine.js b/lib/objects/client/animation-engine.js index d352d4751..e6fc9a298 100644 --- a/lib/objects/client/animation-engine.js +++ b/lib/objects/client/animation-engine.js @@ -17,6 +17,7 @@ const { Logger, sc } = require('@reldens/utils'); const { ObjectsConst } = require('../constants'); +const { GameConst } = require('../../game/constants'); class AnimationEngine { @@ -172,7 +173,7 @@ class AnimationEngine this.sceneSprite.setInteractive({useHandCursor: true}).on('pointerdown', (e) => { // @NOTE: we avoid running the object interactions while any UI element is open, if we click on the UI the // elements in the background scene should not be executed. - if('CANVAS' !== e.downElement.nodeName){ + if(GameConst.CANVAS !== e.downElement.nodeName){ return false; } // @TODO - BETA - CHECK - TempId is a temporal fix for multiple objects case. diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index ff467a1de..39bf16798 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -5,7 +5,6 @@ */ const { ErrorManager, sc } = require('@reldens/utils'); -const {TeamsConst} = require("../constants"); class Team { diff --git a/lib/users/client/player-engine.js b/lib/users/client/player-engine.js index 976772aa5..aea92719e 100644 --- a/lib/users/client/player-engine.js +++ b/lib/users/client/player-engine.js @@ -91,7 +91,7 @@ class PlayerEngine this.players[id].setInteractive({useHandCursor: true}).on('pointerdown', (e) => { // @NOTE: we avoid execute object interactions while the UI element is open, if we click on the UI the other // elements in the background scene should not be executed. - if('CANVAS' !== e.downElement.nodeName){ + if(GameConst.CANVAS !== e.downElement.nodeName){ return false; } // @NOTE: we could send a specific action when the player has been targeted. diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index d9eefc8e0..b9bd4fe39 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -47,6 +47,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/controls/allowPrimaryTouch', '1', @boolean_id); # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); From c332cd386469c7086817d11d7628eab13c6460cb Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 27 Feb 2023 20:34:20 +0100 Subject: [PATCH 16/82] - Reldens - v4.0.0 - Trade sub-actions fix. - UI open and close buttons behavior fix. - Object functions fix. --- lib/actions/client/preloader-handler.js | 8 ++--- lib/game/client/room-events.js | 16 +++++----- lib/game/client/user-interface.js | 4 +-- .../client/exchange/trade-target-action.js | 29 +++++++++++++------ lib/inventory/server/message-actions.js | 20 +++++++++---- .../server/object/type/trader-object.js | 2 +- lib/teams/client/team-create-target-action.js | 26 ++++++++++------- 7 files changed, 65 insertions(+), 40 deletions(-) diff --git a/lib/actions/client/preloader-handler.js b/lib/actions/client/preloader-handler.js index 83ad3f145..b9c333181 100644 --- a/lib/actions/client/preloader-handler.js +++ b/lib/actions/client/preloader-handler.js @@ -101,8 +101,8 @@ class PreloaderHandler if(sc.hasOwn(data.animationData, ['type', 'img']) && 'spritesheet' === data.animationData.type){ this.preloadAnimationsInDirections(data, uiScene); } - if(data.classKey && sc.isObjectFunction(data.classKey, 'prepareAnimation')){ - data.classKey.prepareAnimation({data, uiScene, pack: this}); + if(data.classKey && sc.isFunction(data.classKey['prepareAnimation'])){ + data.classKey['prepareAnimation']({data, uiScene, pack: this}); } } @@ -149,8 +149,8 @@ class PreloaderHandler ? this.createWithMultipleDirections(uiScene, data, animDir) : this.createWithDirection(data, uiScene); } - if(data.classKey && sc.isObjectFunction(data.classKey, 'createAnimation')){ - data.classKey.createAnimation({data, uiScene, pack: this}); + if(data.classKey && sc.isFunction(data.classKey['createAnimation'])){ + data.classKey['createAnimation']({data, uiScene, pack: this}); } } diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 77f17d3b8..cb2a69c77 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -218,12 +218,12 @@ class RoomEvents { if(GameConst.CLOSE_UI_ACTION === message.act && '' !== message.id){ let closeButton = this.gameManager.gameDom.getElement('#box-'+message.id+' .box-close'); - if(closeButton){ - closeButton.click(); - return true; + if(!closeButton){ + Logger.error('Box could not be closed ID "'+message.id+'".'); + return false; } - Logger.error('Box could not be closed ID "'+message.id+'".'); - return false; + closeButton.click(); + return true; } } @@ -243,11 +243,11 @@ class RoomEvents Logger.error('Listener "'+listenerKey+'" is missing.'); return false; } - if(!sc.isObjectFunction(listener, 'executeClientMessageActions')){ + if(!sc.isFunction(listener['executeClientMessageActions'])){ Logger.error('Listener is missing "executeClientMessageActions" method.', listener); return false; } - listener.executeClientMessageActions({message, roomEvents: this}); + listener['executeClientMessageActions']({message, roomEvents: this}); } async runUpdateStats(message) @@ -392,7 +392,7 @@ class RoomEvents this.uiSetTitle(uiBox, props); this.uiSetContent(uiBox, props, uiScene); let dialogContainer = uiBox.getChildByID('box-'+props.id); - dialogContainer.style.display = 'block'; + dialogContainer.style.display = 'none' === dialogContainer.style.display ? 'none' : 'block'; // set box depth over the other boxes: uiBox.setDepth(2); // on dialog display clear the current target: diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index ea94271be..e59187f5e 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -85,7 +85,7 @@ class UserInterface } openButton.id = GameConst.UI_BOX + GameConst.UI_OPEN + '-' + this.id openButton.addEventListener('click', () => { - if(sc.get(this.animProps, 'defaultOpen', false)){ + if(sc.get(this.animProps, 'defaultOpen', true)){ dialogContainer.style.display = 'block'; openButton.style.display = 'none'; if(false !== sc.get(this.animProps, 'depth', false)){ @@ -111,7 +111,7 @@ class UserInterface if(!sc.hasOwn(this.animProps, 'sendCloseMessage') || false === this.animProps['sendCloseMessage']){ uiScene.gameManager.activeRoomEvents.room.send('*', {act: GameConst.CLOSE_UI_ACTION, id: this.id}); } - if(sc.get(this.animProps, 'defaultClose', false)){ + if(sc.get(this.animProps, 'defaultClose', true)){ dialogContainer.style.display = 'none'; if(openButton){ openButton.style.display = 'block'; diff --git a/lib/inventory/client/exchange/trade-target-action.js b/lib/inventory/client/exchange/trade-target-action.js index 3be3b3b4a..117b2b37d 100644 --- a/lib/inventory/client/exchange/trade-target-action.js +++ b/lib/inventory/client/exchange/trade-target-action.js @@ -6,7 +6,7 @@ const { InventoryConst } = require('../../constants'); const { GameConst } = require('../../../game/constants'); -const { sc } = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); class TradeTargetAction { @@ -22,15 +22,26 @@ class TradeTargetAction return false; } let inventoryTradeStartTemplate = uiScene.cache.html.get('inventoryTradeStart'); - uiTarget.getChildByID('box-target').style.display = 'block'; - uiTarget.getChildByID('target-container').innerHTML += gameManager.gameEngine.parseTemplate( - inventoryTradeStartTemplate, - { - playerName: targetName, - playerId: target.id - } + if(!inventoryTradeStartTemplate){ + Logger.critical('Template "inventoryTradeStart" not found.'); + return false; + } + gameManager.gameDom.appendToElement( + '#target-container', + gameManager.gameEngine.parseTemplate( + inventoryTradeStartTemplate, + { + playerName: targetName, + playerId: target.id + } + ) ); - gameManager.gameDom.getElement('.start-trade-'+target.id+' button')?.addEventListener('click', () => { + let tradeStartButton = gameManager.gameDom.getElement('.start-trade-'+target.id+' button'); + if(!tradeStartButton){ + Logger.critical('Trade start button not found for selector: "'+'.start-trade-'+target.id+' button'+'"'); + return false; + } + tradeStartButton?.addEventListener('click', () => { let sendData = {act: InventoryConst.ACTIONS.TRADE_START, id: target.id}; gameManager.room.send('*', sendData); }); diff --git a/lib/inventory/server/message-actions.js b/lib/inventory/server/message-actions.js index bbee4597e..9aceabb7c 100644 --- a/lib/inventory/server/message-actions.js +++ b/lib/inventory/server/message-actions.js @@ -23,7 +23,7 @@ class InventoryMessageActions if(!sc.hasOwn(data, 'id') || !data.id){ return false; } - if(!sc.isObjectFunction(data.id, 'indexOf') || 0 !== data.id.indexOf('trade')){ + if(!sc.isFunction(data.id['indexOf']) || 0 !== data.id.indexOf('trade')){ return false; } return this.closeTradeAction(client, data, room, playerSchema); @@ -147,8 +147,9 @@ class InventoryMessageActions { let subActionParam = sc.get(data, ObjectsConst.TRADE_ACTIONS.SUB_ACTION, false); let mappedSubAction = this.mapSubAction(subActionParam); - if(false === mappedSubAction || !sc.isObjectFunction(PlayerProcessor, mappedSubAction)){ - Logger.critical('Missing mapped sub-action.', mappedSubAction); + let functionExists = sc.isFunction(PlayerProcessor[mappedSubAction]); + if(false === mappedSubAction || !functionExists){ + Logger.critical('Missing mapped sub-action: "'+(mappedSubAction || 'false')+'".', {functionExists}); return false; } let inventoryKey = this.isMyTrade(playerSchema) ? 'A' : 'B'; @@ -201,9 +202,16 @@ class InventoryMessageActions ]; // the exchange key required for the items list is the opposite of the player inventory: let tradeInProgress = playerOwner.tradeInProgress; - let playerToExchangeKey = tradeInProgress.inventories['A'].owner.sessionId === playerTo.sessionId - ? 'A' - : 'B'; + if(!tradeInProgress){ + Logger.critical('Trade not longer in progress.'); + return false; + } + let ownerSessionId = tradeInProgress.inventories['A']?.owner?.sessionId; + if(!ownerSessionId){ + Logger.critical('Trade owner unavailable.'); + return false; + } + let playerToExchangeKey = ownerSessionId === playerTo.sessionId ? 'A' : 'B'; let traderItemsData = this.extractExchangeItemsDataFromInventory( playerToExchangeKey, tradeInProgress diff --git a/lib/objects/server/object/type/trader-object.js b/lib/objects/server/object/type/trader-object.js index 9376d651e..53b8aec22 100644 --- a/lib/objects/server/object/type/trader-object.js +++ b/lib/objects/server/object/type/trader-object.js @@ -142,7 +142,7 @@ class TraderObject extends NpcObject } let subActionParam = sc.get(data, ObjectsConst.TRADE_ACTIONS.SUB_ACTION, false); let mappedSubAction = this.mapSubAction(subActionParam); - if(false !== mappedSubAction && sc.isObjectFunction(Processor, mappedSubAction)){ + if(false !== mappedSubAction && sc.isFunction(Processor[mappedSubAction])){ return await this.processSubAction(mappedSubAction, tradeKey, data, playerSchema, inventoryKey, tradeAction, client); } return await this.initializeTransaction(tradeKey, data, playerSchema, inventoryKey, tradeAction, client); diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index e7a13d284..0d28c2735 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -6,7 +6,7 @@ const { TeamsConst } = require('../constants'); const { GameConst } = require('../../game/constants'); -const { sc } = require('@reldens/utils'); +const { sc, Logger} = require('@reldens/utils'); class TeamTargetActions { @@ -23,15 +23,21 @@ class TeamTargetActions return false; } let teamPlayerActionsTemplate = uiScene.cache.html.get('teamPlayerInvite'); - uiTarget.getChildByID('box-target').style.display = 'block'; - uiTarget.getChildByID('target-container').innerHTML += gameManager.gameEngine.parseTemplate( - teamPlayerActionsTemplate, - { - // @TODO - BETA - Create translations table with a loader and processor. - playerName: targetName, - playerId: target.id, - inviteLabel: gameManager.config.get('team/titles/inviteLabel', TeamsConst.LABELS.INVITE_BUTTON_LABEL) - } + if(!teamPlayerActionsTemplate){ + Logger.critical('Template "teamPlayerInvite" not found.'); + return false; + } + gameManager.gameDom.appendToElement( + '#target-container', + gameManager.gameEngine.parseTemplate( + teamPlayerActionsTemplate, + { + // @TODO - BETA - Create translations table with a loader and processor. + playerName: targetName, + playerId: target.id, + inviteLabel: gameManager.config.get('team/titles/inviteLabel', TeamsConst.LABELS.INVITE_BUTTON_LABEL) + } + ) ); let inviteButton = gameManager.gameDom.getElement('.team-invite-'+target.id+' button'); inviteButton?.addEventListener('click', () => { From 678e7a8b3c7bb0a127139db614a325fad86851b4 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 28 Feb 2023 15:02:31 +0100 Subject: [PATCH 17/82] - Reldens - v4.0.0 - Trade and Teams UIs display fix. --- lib/game/client/room-events.js | 3 ++- .../client/exchange/trade-target-action.js | 2 +- lib/inventory/client/trade-message-handler.js | 23 +++++++++---------- lib/teams/client/team-create-target-action.js | 1 + lib/teams/client/team-message-handler.js | 3 ++- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index cb2a69c77..c38f4a48b 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -392,7 +392,8 @@ class RoomEvents this.uiSetTitle(uiBox, props); this.uiSetContent(uiBox, props, uiScene); let dialogContainer = uiBox.getChildByID('box-'+props.id); - dialogContainer.style.display = 'none' === dialogContainer.style.display ? 'none' : 'block'; + let shouldSetDisplayNone = props.keepCurrentDisplay && 'none' === dialogContainer.style.display; + dialogContainer.style.display = shouldSetDisplayNone ? 'none' : 'block'; // set box depth over the other boxes: uiBox.setDepth(2); // on dialog display clear the current target: diff --git a/lib/inventory/client/exchange/trade-target-action.js b/lib/inventory/client/exchange/trade-target-action.js index 117b2b37d..6ca98c2df 100644 --- a/lib/inventory/client/exchange/trade-target-action.js +++ b/lib/inventory/client/exchange/trade-target-action.js @@ -41,7 +41,7 @@ class TradeTargetAction Logger.critical('Trade start button not found for selector: "'+'.start-trade-'+target.id+' button'+'"'); return false; } - tradeStartButton?.addEventListener('click', () => { + tradeStartButton.addEventListener('click', () => { let sendData = {act: InventoryConst.ACTIONS.TRADE_START, id: target.id}; gameManager.room.send('*', sendData); }); diff --git a/lib/inventory/client/trade-message-handler.js b/lib/inventory/client/trade-message-handler.js index fad7eab98..94a3107a3 100644 --- a/lib/inventory/client/trade-message-handler.js +++ b/lib/inventory/client/trade-message-handler.js @@ -56,11 +56,13 @@ class TradeMessageHandler { // @TODO - BETA - Make all these values configurable. let tradeUiKey = 'trade'+this.message.id; - let uiDoesNotExists = this.createTradeUi(tradeUiKey); - // this will create or reset the ui content: + this.createTradeUi(tradeUiKey); this.roomEvents.initUi({ id: tradeUiKey, - title: 'Trade request from:', + title: this.gameManager.config.getWithoutLogs( + 'client/trade/titles/tradeRequestFromLabel', + 'Trade request from:' + ), content: this.message.from, options: this.gameManager.config.get('client/ui/options/acceptOrDecline'), overrideSendOptions: { @@ -68,12 +70,9 @@ class TradeMessageHandler id: this.message.id } }); - if(uiDoesNotExists){ - this.gameDom.getElement('#opt-2-'+tradeUiKey)?.addEventListener('click', () => { - // this will send the close click action to the server: - this.gameDom.getElement('#box-close-'+tradeUiKey)?.click(); - }); - } + this.gameDom.getElement('#opt-2-'+tradeUiKey)?.addEventListener('click', () => { + this.gameDom.getElement('#box-close-'+tradeUiKey)?.click(); + }); } showTradeBox() @@ -109,8 +108,8 @@ class TradeMessageHandler createTradeUi(tradeUiKey) { - let uiDoesNotExists = !sc.hasOwn(this.roomEvents.tradeUi, tradeUiKey); - if (uiDoesNotExists) { + let tradeUi = sc.get(this.roomEvents.tradeUi, tradeUiKey); + if(!tradeUi){ this.roomEvents.tradeUi[tradeUiKey] = new UserInterface( this.gameManager, {id: tradeUiKey, type: 'trade'}, @@ -119,7 +118,7 @@ class TradeMessageHandler ); this.roomEvents.tradeUi[tradeUiKey].createUiElement(this.uiScene, 'trade'); } - return uiDoesNotExists; + return tradeUi; } updateItemsList(items, container, exchangeData) diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 0d28c2735..006088954 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -20,6 +20,7 @@ class TeamTargetActions let uiScene = gameManager.gameEngine.uiScene; let uiTarget = sc.get(uiScene, 'uiTarget', false); if(false === uiTarget){ + Logger.critical('Missing "uiTarget" on uiScene.'); return false; } let teamPlayerActionsTemplate = uiScene.cache.html.get('teamPlayerInvite'); diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index d7b455ddd..10fee0b10 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -105,7 +105,8 @@ class TeamMessageHandler id: teamUiKey, title, content: '', - options: {} + options: {}, + keepCurrentDisplay: true }); let container = this.gameManager.gameDom.getElement('#box-'+teamUiKey+' .box-content'); if(!container){ From 3655740dbd54e94d49735bbc2418e2718c7adb0a Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 28 Feb 2023 16:12:37 +0100 Subject: [PATCH 18/82] - Reldens - v4.0.0 - Team target fix. --- lib/teams/client/team-create-target-action.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-create-target-action.js index 006088954..6dd4f3699 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-create-target-action.js @@ -13,8 +13,11 @@ class TeamTargetActions showTeamInviteAction(gameManager, target, previousTarget, targetName) { - let playerInTeam = gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id); - if(GameConst.TYPE_PLAYER !== target.type || playerInTeam){ + if( + GameConst.TYPE_PLAYER !== target.type + || gameManager.getCurrentPlayer().playerId === target.id + || gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id) + ){ return false; } let uiScene = gameManager.gameEngine.uiScene; From dfe94cf6cc6b5d4745e0fabe6515e518a06a6e8d Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 1 Mar 2023 21:07:42 +0100 Subject: [PATCH 19/82] - Reldens - v4.0.0 - Team members changing rooms. --- lib/game/client/room-events.js | 31 +++++++------ lib/rooms/server/scene.js | 3 +- .../create-player-clan-handler.js} | 6 +-- .../create-player-team-handler.js | 33 ++++++++++++++ ...nd-player-hit-change-point-team-handler.js | 27 ++++++++++++ .../stats-update-team-handler.js} | 8 ++-- lib/teams/server/plugin.js | 31 ++++++++++--- lib/world/server/collisions-manager.js | 43 +++++++++++++------ 8 files changed, 141 insertions(+), 41 deletions(-) rename lib/teams/server/{subscribers/player-subscriber.js => event-handlers/create-player-clan-handler.js} (92%) create mode 100644 lib/teams/server/event-handlers/create-player-team-handler.js create mode 100644 lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js rename lib/teams/server/{subscribers/stats-update-subscriber.js => event-handlers/stats-update-team-handler.js} (81%) diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index c38f4a48b..7cda1f493 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -164,7 +164,10 @@ class RoomEvents playersOnRemove(player, key) { this.events.emitSync('reldens.playersOnRemove', player, key, this); - key === this.room.sessionId ? this.gameOverReload() : this.removePlayerByKey(key); + if(key === this.room.sessionId){ + return this.gameOverReload(); + } + return this.removePlayerByKey(key); } removePlayerByKey(key) @@ -216,15 +219,16 @@ class RoomEvents async closeBox(message) { - if(GameConst.CLOSE_UI_ACTION === message.act && '' !== message.id){ - let closeButton = this.gameManager.gameDom.getElement('#box-'+message.id+' .box-close'); - if(!closeButton){ - Logger.error('Box could not be closed ID "'+message.id+'".'); - return false; - } - closeButton.click(); - return true; + if(GameConst.CLOSE_UI_ACTION !== message.act || !message.id){ + return false; + } + let closeButton = this.gameManager.gameDom.getElement('#box-'+message.id+' .box-close'); + if(!closeButton){ + Logger.error('Box could not be closed ID "'+message.id+'".'); + return false; } + closeButton.click(); + return true; } async runCustomMessageListener(message) @@ -294,10 +298,11 @@ class RoomEvents closeAllActiveDialogs() { let closeButtons = this.gameManager.gameDom.getElements('.ui-box .box-close'); - if(0 < closeButtons.length){ - for(let closeButton of closeButtons){ - closeButton.click(); - } + if(0 === closeButtons.length){ + return; + } + for(let closeButton of closeButtons){ + closeButton.click(); } } diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index 7c4b9c0b5..fc040983b 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -144,10 +144,8 @@ class RoomScene extends RoomLogin async createPlayerOnScene(client, authResult) { await this.events.emit('reldens.createPlayerBefore', client, authResult, this); - // player creation: let currentPlayer = this.state.createPlayerSchema(authResult, client.sessionId); currentPlayer.persistData = async (params) => { - // persist data in player: await this.events.emit('reldens.playerPersistDataBefore', client, authResult, currentPlayer, params, this); await this.savePlayedTime(currentPlayer); await this.savePlayerState(currentPlayer.sessionId); @@ -509,6 +507,7 @@ class RoomScene extends RoomLogin return true; } // clean up the listeners! + // @TODO - BETA - Emit a new event for the room dispose and use listeners on each other core-plugin. for(let rI of Object.keys(this.roomWorld.respawnAreas)){ let instC = this.roomWorld.respawnAreas[rI].instancesCreated; for(let i of Object.keys(instC)){ diff --git a/lib/teams/server/subscribers/player-subscriber.js b/lib/teams/server/event-handlers/create-player-clan-handler.js similarity index 92% rename from lib/teams/server/subscribers/player-subscriber.js rename to lib/teams/server/event-handlers/create-player-clan-handler.js index 722546970..643ee67e9 100644 --- a/lib/teams/server/subscribers/player-subscriber.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -1,10 +1,10 @@ /** * - * Reldens - PlayerSubscriber + * Reldens - CreatePlayerClanHandler * */ -class PlayerSubscriber +class CreatePlayerClanHandler { static async enrichPlayerWithClan(client, playerSchema, room, events, modelsManager) @@ -42,4 +42,4 @@ class PlayerSubscriber } -module.exports.PlayerSubscriber = PlayerSubscriber; +module.exports.CreatePlayerClanHandler = CreatePlayerClanHandler; diff --git a/lib/teams/server/event-handlers/create-player-team-handler.js b/lib/teams/server/event-handlers/create-player-team-handler.js new file mode 100644 index 000000000..39c129d66 --- /dev/null +++ b/lib/teams/server/event-handlers/create-player-team-handler.js @@ -0,0 +1,33 @@ +/** + * + * Reldens - CreatePlayerTeamHandler + * + */ + +const { TeamUpdatesHandler } = require('../team-updates-handler'); +const { Logger } = require('@reldens/utils'); + +class CreatePlayerTeamHandler +{ + + static async joinExistentTeam(client, playerSchema, room, events, modelsManager, teamsPlugin) + { + let teamId = teamsPlugin.changingRoomPlayers[playerSchema.player_id]?.teamId; + if(!teamId){ + Logger.info('Player "'+playerSchema.playerName+'" (ID "'+playerSchema.player_id+'"), is not in a team.'); + return false; + } + let currentTeam = teamsPlugin.teams[teamId]; + if(!currentTeam){ + Logger.error('Player "'+playerSchema.player_id+'" current team "'+teamId+'"not found.'); + playerSchema.currentTeam = false; + return false; + } + currentTeam.join(playerSchema, client); + playerSchema.currentTeam = teamId; + TeamUpdatesHandler.updateTeamPlayers(currentTeam); + } + +} + +module.exports.CreatePlayerTeamHandler = CreatePlayerTeamHandler; diff --git a/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js new file mode 100644 index 000000000..62efa3a65 --- /dev/null +++ b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js @@ -0,0 +1,27 @@ +/** + * + * Reldens - EndPlayerHitChangePointTeamHandler + * + */ +const {Logger} = require("@reldens/utils"); + +class EndPlayerHitChangePointTeamHandler +{ + + static async savePlayerTeam(playerSchema, teamsPlugin) + { + let teamId = playerSchema.currentTeam; + if(!teamId){ + Logger.info('Player "'+playerSchema.playerName+'" (ID "'+playerSchema.player_id+'"), team not saved.'); + return false; + } + teamsPlugin.changingRoomPlayers[playerSchema.player_id] = { + teamId, + leavingPlayerSessionId: playerSchema.sessionId + }; + teamsPlugin.teams[teamId].leave(playerSchema); + } + +} + +module.exports.EndPlayerHitChangePointTeamHandler = EndPlayerHitChangePointTeamHandler; diff --git a/lib/teams/server/subscribers/stats-update-subscriber.js b/lib/teams/server/event-handlers/stats-update-team-handler.js similarity index 81% rename from lib/teams/server/subscribers/stats-update-subscriber.js rename to lib/teams/server/event-handlers/stats-update-team-handler.js index feac06c66..a2f68c4a3 100644 --- a/lib/teams/server/subscribers/stats-update-subscriber.js +++ b/lib/teams/server/event-handlers/stats-update-team-handler.js @@ -1,16 +1,16 @@ /** * - * Reldens - StatsUpdateSubscriber + * Reldens - StatsUpdateTeamHandler * */ const { TeamUpdatesHandler } = require('../team-updates-handler'); const { Logger, sc } = require('@reldens/utils'); -class StatsUpdateSubscriber +class StatsUpdateTeamHandler { - static async updateTeamData(props) + static async updateTeam(props) { let {teamsPlugin, playerSchema} = props; let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); @@ -28,4 +28,4 @@ class StatsUpdateSubscriber } -module.exports.StatsUpdateSubscriber = StatsUpdateSubscriber; +module.exports.StatsUpdateTeamHandler = StatsUpdateTeamHandler; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 1e68d7a2e..d4e1e7002 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -6,8 +6,10 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamsMessageActions } = require('./message-actions'); -const { PlayerSubscriber } = require('./subscribers/player-subscriber'); -const { StatsUpdateSubscriber } = require('./subscribers/stats-update-subscriber'); +const { CreatePlayerClanHandler } = require('./event-handlers/create-player-clan-handler'); +const { CreatePlayerTeamHandler } = require('./event-handlers/create-player-team-handler'); +const { StatsUpdateTeamHandler } = require('./event-handlers/stats-update-team-handler'); +const { EndPlayerHitChangePointTeamHandler } = require('./event-handlers/end-player-hit-change-point-team-handler'); const { Logger, sc } = require('@reldens/utils'); class TeamsPlugin extends PluginInterface @@ -19,16 +21,35 @@ class TeamsPlugin extends PluginInterface if(!this.events){ Logger.error('EventsManager undefined in TeamsPlugin.'); } + this.teams = sc.get(props, 'teams', {}); + this.changingRoomPlayers = sc.get(props, 'changingRoomPlayers', {}); this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { roomMessageActions.teams = new TeamsMessageActions({teamsPlugin: this}); }); this.events.on('reldens.createPlayerAfter', async (client, authResult, playerSchema, room) => { - await PlayerSubscriber.enrichPlayerWithClan(client, playerSchema, room, this.events, this.modelsManager); + await CreatePlayerClanHandler.enrichPlayerWithClan( + client, + playerSchema, + room, + this.events, + this.modelsManager, + this + ); + await CreatePlayerTeamHandler.joinExistentTeam( + client, + playerSchema, + room, + this.events, + this.modelsManager, + this + ); }); this.events.on('reldens.savePlayerStatsUpdateClient', async (client, playerSchema, room) => { - await StatsUpdateSubscriber.updateTeamData({teamsPlugin: this, playerSchema}); + await StatsUpdateTeamHandler.updateTeam({teamsPlugin: this, playerSchema}); + }); + this.events.on('reldens.endPlayerHitChangePoint', async (event) => { + await EndPlayerHitChangePointTeamHandler.savePlayerTeam(event.playerSchema, this); }); - this.teams = {}; } } diff --git a/lib/world/server/collisions-manager.js b/lib/world/server/collisions-manager.js index 47df37075..d12bf5090 100644 --- a/lib/world/server/collisions-manager.js +++ b/lib/world/server/collisions-manager.js @@ -128,28 +128,33 @@ class CollisionsManager { // @NOTE: we could run specific events when a player collides with another player. // Logger.info(['Hit player!', bodyA.playerId, bodyB.playerId]); + this.room.events.emit('reldens.playerHitPlayer', {bodyA, bodyB}); } playerHitObject(playerBody, otherBody) { + this.room.events.emit('reldens.startPlayerHitObject', {playerBody, otherBody}); // if the player collides with something we need to restart the pathfinder if it was active: if(playerBody.autoMoving && 1 < playerBody.autoMoving.length){ let destPoint = playerBody.autoMoving.pop(); playerBody.moveToPoint({column: destPoint[0], row: destPoint[1]}); } // now the collision manager only run the object hit action: - if(otherBody.roomObject){ + if(otherBody.roomObject && sc.isFunction(otherBody.roomObject.onHit)){ otherBody.roomObject.onHit({bodyA: playerBody, bodyB: otherBody, room: this.room}); } + this.room.events.emit('reldens.endPlayerHitObject', {playerBody, otherBody}); } playerHitWallBegin(playerBody, wall) { // available to add specifics + this.room.events.emit('reldens.playerHitWallBegin', {playerBody, wall}); } playerHitWall(playerBody) { + this.room.events.emit('reldens.startPlayerHitWall', {playerBody}); // @NOTE: we can use wall.material to trigger an action over the player, like: // wall.material = lava > reduce player.hp in every step // if the player collides with something we need to restart the pathfinder if it was active: @@ -161,19 +166,23 @@ class CollisionsManager if(playerBody.world && !playerBody.world.applyGravity){ playerBody.stopFull(true); } + this.room.events.emit('reldens.endPlayerHitWall', {playerBody}); } playerHitObjectEnd(playerBody) { - playerBody.stopFull(true); + let result = {resultValue: true}; + this.room.events.emit('reldens.playerHitObjectEnd', {playerBody, result}); + playerBody.stopFull(result.resultValue); } playerHitChangePoint(playerBody, changePoint) { + this.room.events.emit('reldens.startPlayerHitChangePoint', {collisionsManager: this, playerBody, changePoint}); playerBody.resetAuto(); // check if the player is not changing scenes already: let isChangingScene = sc.get(playerBody, 'isChangingScene', false); - if(true === isChangingScene){ + if(isChangingScene){ // @NOTE: if the player is already changing scene do nothing. Logger.error('Player is busy for a change point: ' + playerBody.playerId); return false; @@ -181,20 +190,26 @@ class CollisionsManager let playerPosition = {x: playerBody.position[0], y: playerBody.position[1]}; this.room.state.positionPlayer(playerBody.playerId, playerPosition); let playerSchema = this.room.getPlayerFromState(playerBody.playerId); - let changeScene = changePoint.changeScenePoint; - let previousScene = playerSchema.state.scene; - let changeData = {prev: previousScene, next: changeScene}; + let changeData = {prev: playerSchema.state.scene, next: changePoint.changeScenePoint}; playerBody.isChangingScene = true; let contactClient = this.room.getClientById(playerBody.playerId); // @NOTE: we do not need to change back the isChangingScene property back to false since in the new // scene a new body will be created with the value set to false by default. this.room.nextSceneInitialPosition(contactClient, changeData).catch((err) => { - Logger.error('nextSceneInitialPosition error: '+err); + Logger.error('There was an error while setting the next scene initial position.', err); + }); + this.room.events.emit('reldens.endPlayerHitChangePoint', { + collisionsManager: this, + playerSchema, + playerBody, + changePoint, + changeData }); } objectHitObject(bodyA, bodyB) { + this.room.events.emit('reldens.startObjectHitObject', {bodyA, bodyB}); // @TODO - BETA - Fix bullet hit bullet. let aPriority = sc.hasOwn(bodyA, 'hitPriority'); let bPriority = sc.hasOwn(bodyB, 'hitPriority'); @@ -205,27 +220,27 @@ class CollisionsManager if(!priorityObject.roomObject){ return; } - priorityObject.roomObject.onHit(onHitData); + if(sc.isFunction(priorityObject.roomObject?.onHit)){ + priorityObject.roomObject.onHit(onHitData); + } + this.room.events.emit('reldens.endObjectHitObject', {bodyA, bodyB, priorityObject}); } getWallBody(evt) { - let bodyA = evt.bodyA, - bodyB = evt.bodyB; + let {bodyA, bodyB} = evt; return bodyA && bodyA.isWall ? bodyA : (bodyB && bodyB.isWall ? bodyB : false); } getObjectBody(evt) { - let bodyA = evt.bodyA, - bodyB = evt.bodyB; + let {bodyA, bodyB} = evt; return bodyA && bodyA.isRoomObject ? bodyA : (bodyB && bodyB.isRoomObject ? bodyB : false); } getPlayerBody(evt) { - let bodyA = evt.bodyA, - bodyB = evt.bodyB; + let {bodyA, bodyB} = evt; return bodyA && bodyA.playerId ? bodyA : (bodyB && bodyB.playerId ? bodyB : false); } From 418b56d1e0eaefee321bc70c2422a3ebbc864ae3 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 5 Mar 2023 19:28:56 +0100 Subject: [PATCH 20/82] - Reldens - v4.0.0 - Team members changing rooms UI fixes. - UserInterface improvements. --- lib/actions/client/skills-ui.js | 1 + lib/chat/client/chat-ui.js | 1 + lib/game/client/game-engine.js | 3 +- lib/game/client/instructions-ui.js | 1 + lib/game/client/minimap-ui.js | 1 + lib/game/client/room-events.js | 15 ++++-- lib/game/client/settings-ui.js | 1 + lib/game/client/ui-factory.js | 2 +- lib/game/client/user-interface.js | 7 ++- lib/game/server/entity-properties.js | 4 +- lib/inventory/client/inventory-ui.js | 2 +- lib/objects/server/object/type/base-object.js | 2 +- lib/rooms/server/scene.js | 21 ++++++-- lib/rooms/server/state.js | 7 ++- lib/teams/client/plugin.js | 7 ++- lib/teams/client/team-message-handler.js | 48 +++++++++---------- lib/teams/client/team-message-listener.js | 8 +++- ...-action.js => team-target-box-enricher.js} | 11 +++-- lib/users/client/player-stats-ui.js | 2 +- lib/world/server/collisions-manager.js | 2 +- theme/default/css/base.scss | 2 +- 21 files changed, 90 insertions(+), 58 deletions(-) rename lib/teams/client/{team-create-target-action.js => team-target-box-enricher.js} (85%) diff --git a/lib/actions/client/skills-ui.js b/lib/actions/client/skills-ui.js index d60eed62a..fb9e3fd2a 100644 --- a/lib/actions/client/skills-ui.js +++ b/lib/actions/client/skills-ui.js @@ -49,6 +49,7 @@ class SkillsUi createUiBox(codeName, depth) { + // @TODO - BETA - Replace by UserInterface. let {uiX, uiY} = this.uiScene.getUiConfig(codeName); let generatedUi = this.uiScene.add.dom(uiX, uiY).createFromCache(codeName); generatedUi.setDepth(depth); diff --git a/lib/chat/client/chat-ui.js b/lib/chat/client/chat-ui.js index ed1bd8933..54010c257 100644 --- a/lib/chat/client/chat-ui.js +++ b/lib/chat/client/chat-ui.js @@ -26,6 +26,7 @@ class ChatUi createUi() { + // @TODO - BETA - Replace by UserInterface. let {uiX, uiY} = this.uiScene.getUiConfig('chat'); this.uiChat = this.uiScene.add.dom(uiX, uiY).createFromCache('chat'); this.uiScene.elementsUi['chat'] = this.uiChat; diff --git a/lib/game/client/game-engine.js b/lib/game/client/game-engine.js index 9a8fd85f0..fc8d8b8e5 100644 --- a/lib/game/client/game-engine.js +++ b/lib/game/client/game-engine.js @@ -49,7 +49,8 @@ class GameEngine extends Game this.scale.setGameSize(newWidth, newHeight); for(let key of Object.keys(this.uiScene.elementsUi)){ let uiElement = this.uiScene.elementsUi[key]; - let {uiX, uiY} = this.uiScene.getUiConfig(key, newWidth, newHeight); + let positionKey = sc.get(this.uiScene.userInterfaces[key], 'uiPositionKey', key); + let {uiX, uiY} = this.uiScene.getUiConfig(positionKey, newWidth, newHeight); uiElement.x = uiX; uiElement.y = uiY; } diff --git a/lib/game/client/instructions-ui.js b/lib/game/client/instructions-ui.js index d46cace19..767ffb712 100644 --- a/lib/game/client/instructions-ui.js +++ b/lib/game/client/instructions-ui.js @@ -9,6 +9,7 @@ class InstructionsUi createInstructions(instConfig, scenePreloader) { + // @TODO - BETA - Replace by UserInterface. scenePreloader.elementsUi['instructions'] = scenePreloader.add.dom(instConfig.uiX, instConfig.uiY) .createFromCache('instructions'); let instructionsBox = scenePreloader.gameManager.gameDom.getElement('#instructions'); diff --git a/lib/game/client/minimap-ui.js b/lib/game/client/minimap-ui.js index 49eb6b5ae..8c787c819 100644 --- a/lib/game/client/minimap-ui.js +++ b/lib/game/client/minimap-ui.js @@ -9,6 +9,7 @@ class MinimapUi createMinimap(minimapConfig, scenePreloader) { + // @TODO - BETA - Replace by UserInterface. scenePreloader.elementsUi['minimap'] = scenePreloader.add.dom(minimapConfig.uiX, minimapConfig.uiY) .createFromCache('minimap'); let openButton = scenePreloader.elementsUi['minimap'].getChildByProperty('id', 'minimap-open'); diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 7cda1f493..e81047a4e 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -26,8 +26,6 @@ class RoomEvents // @TODO - BETA - Move the following inside a single property called "metadata". this.objectsUi = {}; this.tradeUi = {}; - this.teamUi = {}; - this.currentTeam = false; } async activateRoom(room, previousScene = false) @@ -284,8 +282,8 @@ class RoomEvents ){ return false; } + await this.events.emit('reldens.startChangedScene', {message, roomEvents: this}); this.closeAllActiveDialogs(); - await this.events.emit('reldens.changedScene', message, this); let currentScene = this.getActiveScene(); // if other users enter the current scene we need to add them: let {id, x, y, dir, playerName, playedTime, avatarKey} = message; @@ -293,6 +291,7 @@ class RoomEvents let leftOff = this.gameManager.config.get('client/players/size/leftOffset'); let addPlayerData = {x: (x - leftOff), y: (y - topOff), dir, playerName, playedTime, avatarKey}; currentScene.player.addPlayer(id, addPlayerData); + await this.events.emit('reldens.endChangedScene', {message, roomEvents: this}); } closeAllActiveDialogs() @@ -341,12 +340,13 @@ class RoomEvents this.gameManager.gameDom.getElement('#game-over').classList.remove('hidden'); } - roomOnLeave(code) + async roomOnLeave(code) { // @TODO - BETA - Improve disconnection handler. if(code > 1001 && !this.gameManager.gameOver && !this.gameManager.forcedDisconnection){ Logger.error('There was a connection error.', ['Error Code:', code]); } + await this.events.emit('reldens.playerLeftScene', {code, roomEvents: this}); // @NOTE: the client can initiate the disconnection, this is also triggered when the users change the room. } @@ -357,6 +357,7 @@ class RoomEvents } let currentScene = this.getActiveScene(); if(!currentScene.player || !sc.hasOwn(currentScene.player.players, this.room.sessionId)){ + // @TODO - BETA - Refactor and fix cases. Logger.error('For some reason you hit this case which should not happen.', this.room, currentScene); return false; } @@ -410,7 +411,7 @@ class RoomEvents uiSetTitle(uiBox, props) { let newTitle = sc.get(props, 'title', false); - if(false === sc.get(props, 'title', false)){ + if(false === newTitle){ return false; } let boxTitle = uiBox.getChildByProperty('className', 'box-title'); @@ -570,6 +571,7 @@ class RoomEvents return false; } for(let i of this.playersKeysFromState(room)){ + // @TODO - BETA - Refactor and extract Colyseus into a driver. let tmp = room.state.players.get(i); if(!tmp.sessionId || tmp.sessionId === room.sessionId){ continue; @@ -588,16 +590,19 @@ class RoomEvents playerByIdFromState(room, i) { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return room.state.players.get(i); } playersCountFromState(room) { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return room.state.players.size; } playersKeysFromState(room) { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return Array.from(room.state.players.keys()); } diff --git a/lib/game/client/settings-ui.js b/lib/game/client/settings-ui.js index 1cde7a2a6..24435257f 100644 --- a/lib/game/client/settings-ui.js +++ b/lib/game/client/settings-ui.js @@ -9,6 +9,7 @@ class SettingsUi createSettings(settingsConfig, scenePreloader) { + // @TODO - BETA - Replace by UserInterface. scenePreloader.elementsUi['settings'] = scenePreloader.add.dom(settingsConfig.uiX, settingsConfig.uiY) .createFromCache('settings'); let settingsTemplate = scenePreloader.cache.html.get('settings-content'); diff --git a/lib/game/client/ui-factory.js b/lib/game/client/ui-factory.js index ff4ac3314..eeded5ea5 100644 --- a/lib/game/client/ui-factory.js +++ b/lib/game/client/ui-factory.js @@ -17,7 +17,7 @@ class UiFactory create(uiCodeName, depth, defaultOpen, defaultClose, openCallback, closeCallback) { - // @TODO - BETA - Remove and replace by UserInterface. + // @TODO - BETA - Replace by UserInterface. let {uiX, uiY} = this.uiScene.getUiConfig(uiCodeName); let newUiObject = this.uiScene.add.dom(uiX, uiY).createFromCache(uiCodeName); let openButton = newUiObject.getChildByProperty('id', uiCodeName+GameConst.UI_OPEN); diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index e59187f5e..984e09782 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -44,7 +44,10 @@ class UserInterface } let objectElementId = 'box-'+this.id; let gameDom = uiScene.gameManager.gameDom; - let dialogBox = sc.get(uiScene.elementsUi, this.id, this.createDialogBox(uiScene, templateKey)) + if(sc.get(uiScene.elementsUi, this.id)){ + return this; + } + let dialogBox = this.createDialogBox(uiScene, templateKey); this.createBoxContent(uiScene, templateKey, dialogBox); let dialogContainer = gameDom.getElement('.ui-box.ui-dialog-box', dialogBox.node); if(!dialogContainer){ @@ -120,7 +123,7 @@ class UserInterface dialogBox.setDepth(1); } } - if (sc.isFunction(this.animProps['closeCallback'])) { + if(sc.isFunction(this.animProps['closeCallback'])){ this.animProps['closeCallback'](); } }); diff --git a/lib/game/server/entity-properties.js b/lib/game/server/entity-properties.js index 849ff5e45..c84fd65a5 100644 --- a/lib/game/server/entity-properties.js +++ b/lib/game/server/entity-properties.js @@ -11,12 +11,12 @@ class EntityProperties static propertiesDefinition() { - Logger.error('Method not implemented propertiesDefinition().'); + Logger.alert('Method not implemented propertiesDefinition().'); } static propertiesConfig(extraProps) { - Logger.error('Method not implemented propertiesConfig().', extraProps); + Logger.alert('Method not implemented propertiesConfig().', extraProps); } } diff --git a/lib/inventory/client/inventory-ui.js b/lib/inventory/client/inventory-ui.js index 216c1c527..91d770a6b 100644 --- a/lib/inventory/client/inventory-ui.js +++ b/lib/inventory/client/inventory-ui.js @@ -12,7 +12,7 @@ class InventoryUi extends UiFactory createUi() { - // @TODO - BETA - Remove and replace by UserInterface. + // @TODO - BETA - Replace by UserInterface. this.create('inventory', 5, true, true, null, () => { this.inventoryVisibility('inventory'); }); diff --git a/lib/objects/server/object/type/base-object.js b/lib/objects/server/object/type/base-object.js index 21e54fb32..abf29dca0 100644 --- a/lib/objects/server/object/type/base-object.js +++ b/lib/objects/server/object/type/base-object.js @@ -75,7 +75,7 @@ class BaseObject extends InteractionArea async runAdditionalSetup() { // @NOTE: implement what you need here. - Logger.alert('Method not implemented "runAdditionalSetup" on: '+this.key); + Logger.info('Method not implemented "runAdditionalSetup" on: '+this.key); } } diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index fc040983b..b9fb627e5 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -35,9 +35,7 @@ class RoomScene extends RoomLogin // related object instances will be removed when the room is disposed. let objectsManagerConfig = Object.assign({events: this.events}, options); this.objectsManager = new ObjectsManager(objectsManagerConfig); - // load the objects from the storage: await this.objectsManager.loadObjectsByRoomId(options.roomData.roomId); - // generate object instances: if(this.objectsManager.roomObjectsData){ await this.objectsManager.generateObjects(); } @@ -51,7 +49,6 @@ class RoomScene extends RoomLogin this.customData = options.roomData.customData || {}; WorldConfig.mapWorldConfigValues(this, this.config); this.allowSimultaneous = this.config.get('client/general/controls/allowSimultaneousKeys', true); - // create world: await this.createWorld(options.roomData, this.objectsManager); // the collisions manager has to be initialized after the world was created: this.collisionsManager = new CollisionsManager(this); @@ -128,16 +125,19 @@ class RoomScene extends RoomLogin playerByIdFromState(id) { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return this.state.players.get(id); } playersCountInState() { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return this.state.players.size; } playersKeysFromState() { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return Array.from(this.state.players.keys()); } @@ -363,6 +363,20 @@ class RoomScene extends RoomLogin playedTime: currentPlayer.playedTime, avatarKey: currentPlayer.avatarKey }); + /* @TODO - BETA - Make played time broadcast optional. + this.send('*', { + act: GameConst.CHANGED_SCENE, + id: client.sessionId, + scene: currentPlayer.state.scene, + prev: data.prev, + x: currentPlayer.state.x, + y: currentPlayer.state.y, + dir: currentPlayer.state.dir, + playerName: currentPlayer.playerName, + playedTime: currentPlayer.playedTime, + avatarKey: currentPlayer.avatarKey + }); + */ // remove body from server world: let bodyToRemove = currentPlayer.physicalBody; this.roomWorld.removeBody(bodyToRemove); @@ -480,6 +494,7 @@ class RoomScene extends RoomLogin getPlayerFromState(playerIndex) { + // @TODO - BETA - Refactor and extract Colyseus into a driver. return this.state.players.get(playerIndex); } diff --git a/lib/rooms/server/state.js b/lib/rooms/server/state.js index 4f519a9de..47f8d8e38 100644 --- a/lib/rooms/server/state.js +++ b/lib/rooms/server/state.js @@ -44,8 +44,11 @@ class State extends Schema positionPlayer(id, data) { - if(!sc.hasOwn(this.players, id)){ - Logger.error('Player not found! ID: '+id); + // @TODO - BETA - Refactor and extract Colyseus into a driver. + if(!this.players.get(id)){ + let stackHolder = {}; + Error.captureStackTrace(stackHolder, 'positionPlayer'); + Logger.error('Player not found! ID: '+id, stackHolder.stack); return false; } this.players[id].state.mov = false; diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 4d3f3d175..d9ba51f81 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -6,7 +6,7 @@ // const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); -const { TeamTargetActions } = require('./team-create-target-action'); +const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); const { TemplatesHandler } = require('./templates-handler'); const { TeamsConst } = require('../constants'); @@ -17,7 +17,6 @@ class TeamsPlugin extends PluginInterface setup(props) { - this.teamTargetActions = new TeamTargetActions(); this.gameManager = sc.get(props, 'gameManager', false); // this.clanUi = new UserInterface(this.gameManager, {id: TeamsConst.CLAN_KEY, type: TeamsConst.CLAN_KEY}); if (!this.gameManager) { @@ -31,14 +30,14 @@ class TeamsPlugin extends PluginInterface TemplatesHandler.preloadTemplates(preloadScene); }); this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { - this.teamTargetActions.showTeamInviteAction(this.gameManager, target, previousTarget, targetName); + TeamTargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); }); this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } fetchTeamPlayerBySessionId(sessionId) { - let currentTeam = this.gameManager.activeRoomEvents.currentTeam; + let currentTeam = this.gameManager.gameEngine.uiScene.currentTeam; if(!currentTeam){ return false; } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 10fee0b10..66f19e3bb 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -6,7 +6,7 @@ const { UserInterface } = require('../../game/client/user-interface'); const { TeamsConst } = require('../constants'); -const { ErrorManager, Logger, sc } = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); class TeamMessageHandler { @@ -18,37 +18,38 @@ class TeamMessageHandler this.gameManager = this.roomEvents?.gameManager; this.gameDom = this.gameManager?.gameDom; this.uiScene = this.gameManager?.gameEngine?.uiScene; - this.validate(); } validate() { if(!this.roomEvents){ - ErrorManager.error('Missing RoomEvents.'); + Logger.error('Missing RoomEvents.'); + return false; } if(!this.message){ - ErrorManager.error('Missing message.'); + Logger.error('Missing message.'); + return false; } if(!this.gameManager){ - ErrorManager.error('Missing GameManager.'); + Logger.error('Missing GameManager.'); + return false; } if(!this.uiScene){ - ErrorManager.error('Missing UI Scene.'); + Logger.error('Missing UI Scene.'); + return false; } + return true; } updateContents() { - if(-1 === this.message.act.indexOf(TeamsConst.TEAM_PREF)){ + if(0 !== this.message.act.indexOf(TeamsConst.TEAM_PREF)){ return false; } if(TeamsConst.ACTIONS.TEAM_INVITE === this.message.act){ return this.showTeamRequest(); } if(TeamsConst.ACTIONS.TEAM_UPDATE === this.message.act){ - if(!this.gameManager.gameDom.getElement('#box-'+this.teamUiKey()+' .box-content')){ - this.gameManager.gameEngine.clearTarget(); - } return this.showTeamBox(); } if(TeamsConst.ACTIONS.TEAM_LEFT === this.message.act){ @@ -84,8 +85,7 @@ class TeamMessageHandler uiElement.removeElement(); delete this.uiScene.userInterfaces[this.teamUiKey()]; delete this.uiScene.elementsUi[this.teamUiKey()]; - delete this.roomEvents.teamUi; - this.gameManager.activeRoomEvents.currentTeam = false; + this.uiScene.currentTeam = false; } teamUiKey() @@ -101,40 +101,36 @@ class TeamMessageHandler 'client/team/labels/leaderNameTitle', TeamsConst.LABELS.LEADER_NAME_TITLE ).replace('%leaderName', this.message.leaderName); - this.roomEvents.initUi({ - id: teamUiKey, - title, - content: '', - options: {}, - keepCurrentDisplay: true - }); let container = this.gameManager.gameDom.getElement('#box-'+teamUiKey+' .box-content'); if(!container){ Logger.error('Missing container: "#box-'+teamUiKey+' .box-content".'); return false; } + let uiBox = this.uiScene.elementsUi[teamUiKey]; + this.roomEvents.uiSetTitle(uiBox, {title}); + this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); let players = sc.get(this.message, 'players', false); - this.roomEvents.currentTeam = players; + this.uiScene.currentTeam = players; this.updateTeamBox(players, container); } createTeamUi(teamUiKey) { - let teamsUi = sc.get(this.roomEvents.teamUi, teamUiKey); + let teamsUi = sc.get(this.uiScene.userInterfaces, teamUiKey); if(teamsUi){ return teamsUi; } - if(!this.roomEvents.teamUi){ - this.roomEvents.teamUi = {}; + if(!this.uiScene.userInterfaces){ + this.uiScene.userInterfaces = {}; } - this.roomEvents.teamUi[teamUiKey] = new UserInterface( + this.uiScene.userInterfaces[teamUiKey] = new UserInterface( this.gameManager, {id: teamUiKey, type: TeamsConst.KEY, defaultOpen: true, defaultClose: true}, 'assets/features/teams/templates/ui-teams.html', TeamsConst.KEY ); - this.roomEvents.teamUi[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); - return this.roomEvents.teamUi[teamUiKey]; + this.uiScene.userInterfaces[teamUiKey].createUiElement(this.uiScene, TeamsConst.KEY); + return this.uiScene.userInterfaces[teamUiKey]; } updateTeamBox(players, container) diff --git a/lib/teams/client/team-message-listener.js b/lib/teams/client/team-message-listener.js index b565f0237..981dd2152 100644 --- a/lib/teams/client/team-message-listener.js +++ b/lib/teams/client/team-message-listener.js @@ -22,8 +22,12 @@ class TeamMessageListener Logger.error('Missing RoomEvents on TeamMessageListener.', props); return false; } - let tradeMessageHandler = new TeamMessageHandler({roomEvents, message}); - tradeMessageHandler.updateContents(); + let teamMessageHandler = new TeamMessageHandler({roomEvents, message}); + if(!teamMessageHandler.validate()){ + Logger.error('Invalid TeamMessageHandler', teamMessageHandler); + return false; + } + teamMessageHandler.updateContents(); } } diff --git a/lib/teams/client/team-create-target-action.js b/lib/teams/client/team-target-box-enricher.js similarity index 85% rename from lib/teams/client/team-create-target-action.js rename to lib/teams/client/team-target-box-enricher.js index 6dd4f3699..be272130d 100644 --- a/lib/teams/client/team-create-target-action.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -1,17 +1,17 @@ /** * - * Reldens - TradeTargetAction + * Reldens - TeamTargetBoxEnricher * */ const { TeamsConst } = require('../constants'); const { GameConst } = require('../../game/constants'); -const { sc, Logger} = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); -class TeamTargetActions +class TeamTargetBoxEnricher { - showTeamInviteAction(gameManager, target, previousTarget, targetName) + static appendTeamInviteButton(gameManager, target, previousTarget, targetName) { if( GameConst.TYPE_PLAYER !== target.type @@ -48,9 +48,10 @@ class TeamTargetActions let sendData = {act: TeamsConst.ACTIONS.TEAM_INVITE, id: target.id}; gameManager.room.send('*', sendData); inviteButton.style.display = 'none'; + gameManager.gameEngine.clearTarget(); }); } } -module.exports.TeamTargetActions = TeamTargetActions; +module.exports.TeamTargetBoxEnricher = TeamTargetBoxEnricher; diff --git a/lib/users/client/player-stats-ui.js b/lib/users/client/player-stats-ui.js index 1292015f7..7f07454d1 100644 --- a/lib/users/client/player-stats-ui.js +++ b/lib/users/client/player-stats-ui.js @@ -22,7 +22,7 @@ class PlayerStatsUi scenePreloader.load.html('playerStat', 'assets/html/player-stat.html'); }); this.events.on('reldens.beforeCreateUiScene', (scenePreloader) => { - // @TODO - BETA - Replace by UiFactory. + // @TODO - BETA - Replace by UserInterface. let statsUi = scenePreloader.getUiConfig('playerStats'); if(!statsUi.enabled){ return false; diff --git a/lib/world/server/collisions-manager.js b/lib/world/server/collisions-manager.js index d12bf5090..cb71350ee 100644 --- a/lib/world/server/collisions-manager.js +++ b/lib/world/server/collisions-manager.js @@ -184,7 +184,7 @@ class CollisionsManager let isChangingScene = sc.get(playerBody, 'isChangingScene', false); if(isChangingScene){ // @NOTE: if the player is already changing scene do nothing. - Logger.error('Player is busy for a change point: ' + playerBody.playerId); + Logger.alert('Player is busy for a change point: ' + playerBody.playerId); return false; } let playerPosition = {x: playerBody.position[0], y: playerBody.position[1]}; diff --git a/theme/default/css/base.scss b/theme/default/css/base.scss index cd4eb3990..367b6e2a7 100644 --- a/theme/default/css/base.scss +++ b/theme/default/css/base.scss @@ -294,7 +294,7 @@ form { .box-target { display: none; position: relative; - top: 150px; + top: 170px; left: 10px; #target-container { From 2618396a6e255d2786dc2cb0ec298048c5fd0a68 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 5 Mar 2023 19:35:34 +0100 Subject: [PATCH 21/82] - Reldens - v4.0.0 - Log level fixes. - Clear room world interval. --- lib/rooms/server/scene.js | 7 ++++--- lib/world/server/collisions-manager.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index b9fb627e5..187af2f96 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -231,7 +231,7 @@ class RoomScene extends RoomLogin } // if player stopped: if(GameConst.STOP === messageData.act){ - this.clearIntervals(); + this.clearMovementIntervals(); bodyToMove.stopMove(); } let isPointer = GameConst.POINTER === messageData.act && this.config.get('client/players/tapMovement/enabled'); @@ -242,7 +242,7 @@ class RoomScene extends RoomLogin } } - clearIntervals() + clearMovementIntervals() { let intervalKeys = Object.keys(this.movementInterval); for(let i of intervalKeys){ @@ -515,7 +515,8 @@ class RoomScene extends RoomLogin onDispose() { Logger.info('ON-DISPOSE Room: ' + this.roomName); - this.clearIntervals(); + this.clearMovementIntervals(); + clearInterval(this.roomWorld.worldTimer); // @TODO - BETA - Replace this by a master key related to the room ID and just remove all the events related // to this room. if(!this.roomWorld.respawnAreas){ diff --git a/lib/world/server/collisions-manager.js b/lib/world/server/collisions-manager.js index cb71350ee..b7b964498 100644 --- a/lib/world/server/collisions-manager.js +++ b/lib/world/server/collisions-manager.js @@ -184,7 +184,7 @@ class CollisionsManager let isChangingScene = sc.get(playerBody, 'isChangingScene', false); if(isChangingScene){ // @NOTE: if the player is already changing scene do nothing. - Logger.alert('Player is busy for a change point: ' + playerBody.playerId); + Logger.info('Player is busy for a change point: ' + playerBody.playerId); return false; } let playerPosition = {x: playerBody.position[0], y: playerBody.position[1]}; From 36cc9be5437fe5b24a73643e48536722833d8152 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 5 Mar 2023 19:53:43 +0100 Subject: [PATCH 22/82] - Reldens - v4.0.0 - Team and clan icons. --- .../assets/features/teams/assets/clan.png | Bin 0 -> 1196 bytes .../assets/features/teams/assets/team.png | Bin 29567 -> 2248 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 theme/default/assets/features/teams/assets/clan.png diff --git a/theme/default/assets/features/teams/assets/clan.png b/theme/default/assets/features/teams/assets/clan.png new file mode 100644 index 0000000000000000000000000000000000000000..ed56004c23586890acdbac666b3218e2b326cd67 GIT binary patch literal 1196 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 Date: Mon, 6 Mar 2023 16:46:15 +0100 Subject: [PATCH 23/82] - Reldens - v4.0.0 - Clan DB structure. - Team events. --- lib/teams/server/message-actions.js | 6 ++--- lib/teams/server/message-actions/team-join.js | 25 ++++++++++++++--- .../server/message-actions/team-leave.js | 22 ++++++++++++++- .../server/message-actions/try-team-start.js | 8 +++++- migrations/production/beta.26-sql-update.sql | 27 +++++++++++++++++++ 5 files changed, 80 insertions(+), 8 deletions(-) diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/message-actions.js index aa6349bb0..8df340577 100644 --- a/lib/teams/server/message-actions.js +++ b/lib/teams/server/message-actions.js @@ -27,15 +27,15 @@ class TeamsMessageActions return false; } if(TeamsConst.ACTIONS.TEAM_INVITE === data.act){ - TryTeamStart.execute(client, data, room, playerSchema); + await TryTeamStart.execute(client, data, room, playerSchema, this.teamsPlugin); return true; } if(TeamsConst.ACTIONS.TEAM_ACCEPTED === data.act && '1' === data.value){ - TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); + await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); return true; } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); + await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); return true; } } diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index ab2a2432f..ccf2a1fd8 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -11,7 +11,7 @@ const { Logger } = require('@reldens/utils'); class TeamJoin { - static execute(client, data, room, playerSchema, teamsPlugin) + static async execute(client, data, room, playerSchema, teamsPlugin) { if(playerSchema.sessionId === data.id){ Logger.info('The player is trying to join a team with himself.', playerSchema.sessionId, data); @@ -30,15 +30,34 @@ class TeamJoin Logger.error('Player team owner client not found.', teamOwnerClient, data); return false; } - let currentTeam = teamsPlugin.teams[teamOwnerClient.id] || new Team({ + let teamProps = { owner: teamOwnerPlayer, ownerClient: teamOwnerClient.client, sharedProperties: room.config.get('client/ui/teams/sharedProperties') - }); + }; + let currentTeam = teamsPlugin.teams[teamOwnerClient.id]; + if(!currentTeam){ + let beforeCreateEvent = {teamProps, teamsPlugin, continueBeforeCreate: true}; + await teamsPlugin.event.emit('reldens.beforeTeamCreate', beforeCreateEvent); + if(!beforeCreateEvent.continueBeforeCreate){ + return false; + } + currentTeam = new Team(teamProps); + } + let eventBeforeJoin = {currentTeam, teamsPlugin, continueBeforeJoin: true}; + await teamsPlugin.event.emit('reldens.beforeTeamJoin', eventBeforeJoin); + if(!eventBeforeJoin.continueBeforeJoin){ + return false; + } currentTeam.join(playerSchema, client); teamOwnerPlayer.currentTeam = teamOwnerClient.id; playerSchema.currentTeam = teamOwnerClient.id; teamsPlugin.teams[teamOwnerClient.id] = currentTeam; + let eventBeforeJoinUpdate = {currentTeam, teamsPlugin, continueBeforeJoinUpdate: true}; + await teamsPlugin.event.emit('reldens.beforeTeamUpdatePlayers', eventBeforeJoinUpdate); + if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ + return false; + } TeamUpdatesHandler.updateTeamPlayers(currentTeam); } diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index a58b546ca..80eaa0353 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -11,8 +11,9 @@ const { Logger } = require('@reldens/utils'); class TeamLeave { - static execute(client, data, room, playerSchema, teamsPlugin) + static async execute(client, data, room, playerSchema, teamsPlugin) { + await teamsPlugin.event.emit('reldens.teamLeave', {client, data, room, playerSchema, teamsPlugin}); let teamId = playerSchema.currentTeam; if(!teamId){ return false; @@ -31,13 +32,32 @@ class TeamLeave id: currentTeam.ownerClient.id, listener: TeamsConst.KEY }; + await teamsPlugin.event.emit('reldens.teamLeaveBeforeSendUpdate', { + playerId, + sendUpdate, + client, + data, + room, + playerSchema, + teamsPlugin + }); currentTeam.clients[playerId].send('*', sendUpdate); currentTeam.leave(currentTeam.players[playerId]); } if(0 === Object.keys(currentTeam.players).length){ + let event = {client, data, room, playerSchema, teamsPlugin, continueDisband: true}; + await teamsPlugin.event.emit('reldens.beforeTeamDisband', event); + if(!event.continueDisband){ + return false; + } delete teamsPlugin.teams[teamId]; return true; } + let event = {client, data, room, playerSchema, teamsPlugin, continueLeave: true}; + await teamsPlugin.event.emit('reldens.beforeTeamDisband', event); + if(!event.continueLeave){ + return false; + } TeamUpdatesHandler.updateTeamPlayers(currentTeam); return true; } diff --git a/lib/teams/server/message-actions/try-team-start.js b/lib/teams/server/message-actions/try-team-start.js index 5766d1d3d..a75be59ff 100644 --- a/lib/teams/server/message-actions/try-team-start.js +++ b/lib/teams/server/message-actions/try-team-start.js @@ -9,7 +9,8 @@ const { Logger, sc } = require('@reldens/utils'); class TryTeamStart { - static execute(client, data, room, playerSchema) + + static async execute(client, data, room, playerSchema, teamsPlugin) { if(playerSchema.sessionId === data.id){ Logger.info('The player is trying to team up with himself.', playerSchema.sessionId, data); @@ -26,6 +27,11 @@ class TryTeamStart from: playerSchema.playerName, id: playerSchema.sessionId, }; + let event = {client, data, room, playerSchema, teamsPlugin, continueStart: true}; + await teamsPlugin.event.emit('reldens.tryTeamStart', event); + if(!event.continueStart){ + return false; + } toPlayer.client.send('*', sendData); } } diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index b9bd4fe39..1979baf19 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -73,6 +73,33 @@ CREATE TABLE `clan_members` ( INDEX `FK__players` (`player_id`) USING BTREE ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; +CREATE TABLE `clan_levels` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `key` INT(10) UNSIGNED NOT NULL, + `label` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `required_experience` BIGINT(20) UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) COLLATE='utf8mb3_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; + +CREATE TABLE `clan_levels_modifiers` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `level_id` INT(10) UNSIGNED NOT NULL, + `key` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `property_key` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `operation` INT(10) UNSIGNED NOT NULL, + `value` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `minValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', + `maxValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', + `minProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', + `maxProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', + PRIMARY KEY (`id`) USING BTREE, + INDEX `modifier_id` (`key`) USING BTREE, + INDEX `level_key` (`level_id`) USING BTREE, + INDEX `FK_clan_levels_modifiers_operation_types` (`operation`) USING BTREE, + CONSTRAINT `FK_clan_levels_modifiers_operation_types` FOREIGN KEY (`operation`) REFERENCES `operation_types` (`key`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `FK_clan_levels_modifiers_clan_levels` FOREIGN KEY (`level_id`) REFERENCES `clan_levels` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION +) COLLATE='utf8mb3_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; + # Inventory tables fix: ALTER TABLE `items_inventory` DROP FOREIGN KEY `FK_items_inventory_items_item`; ALTER TABLE `items_item` DROP FOREIGN KEY `FK_items_item_items_group`; From 9b975f47b8a666f24d97a377f9dd1cd143639e5e Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 6 Mar 2023 16:47:33 +0100 Subject: [PATCH 24/82] - Reldens - v4.0.0 - Removed clan comments. --- lib/teams/client/plugin.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index d9ba51f81..d3057a349 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -4,7 +4,6 @@ * */ -// const { UserInterface } = require('../../game/client/user-interface'); const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); @@ -18,7 +17,6 @@ class TeamsPlugin extends PluginInterface setup(props) { this.gameManager = sc.get(props, 'gameManager', false); - // this.clanUi = new UserInterface(this.gameManager, {id: TeamsConst.CLAN_KEY, type: TeamsConst.CLAN_KEY}); if (!this.gameManager) { Logger.error('Game Manager undefined in TeamsPlugin.'); } From 5ab80a569b1bc730a8b9bda5572dfb97d2663a89 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 10 Mar 2023 16:48:34 +0100 Subject: [PATCH 25/82] - Reldens - v4.0.0 - Server Manager improvements. - Client improvements. - Noted TODOs. - Clan entities, models and players enrich. --- .env.dist | 3 + lib/game/client/scene-preloader.js | 2 +- lib/game/constants.js | 7 +- .../{app-server.js => app-server-factory.js} | 17 +-- lib/game/server/manager.js | 121 ++++++++++-------- lib/game/server/theme-manager.js | 55 +++++--- lib/inventory/client/plugin.js | 22 ++-- lib/teams/client/clan-initializer-handler.js | 27 ++++ lib/teams/client/plugin.js | 4 + lib/teams/constants.js | 7 + lib/teams/server/clan.js | 42 ++++++ lib/teams/server/entities-config.js | 24 ++++ lib/teams/server/entities-translations.js | 14 ++ lib/teams/server/entities/clan-entity.js | 45 +++++++ .../server/entities/clan-levels-entity.js | 44 +++++++ .../entities/clan-levels-modifiers-entity.js | 75 +++++++++++ .../server/entities/clan-members-entity.js | 43 +++++++ .../create-player-clan-handler.js | 57 +++++---- .../create-player-team-handler.js | 2 +- .../stats-update-team-handler.js | 1 - .../models/objection-js/clan-levels-model.js | 35 +++++ .../clan-levels-modifiers-model.js | 34 +++++ .../models/objection-js/clan-members-model.js | 43 +++++++ .../server/models/objection-js/clan-model.js | 44 +++++++ .../registered-models-objection-js.js | 26 ++++ lib/teams/server/plugin.js | 22 +--- lib/teams/server/team.js | 1 + lib/users/server/player.js | 13 +- theme/index.js.dist | 16 ++- theme/plugins/server-plugin.js | 2 +- 30 files changed, 693 insertions(+), 155 deletions(-) rename lib/game/server/{app-server.js => app-server-factory.js} (81%) create mode 100644 lib/teams/client/clan-initializer-handler.js create mode 100644 lib/teams/server/clan.js create mode 100644 lib/teams/server/entities-config.js create mode 100644 lib/teams/server/entities-translations.js create mode 100644 lib/teams/server/entities/clan-entity.js create mode 100644 lib/teams/server/entities/clan-levels-entity.js create mode 100644 lib/teams/server/entities/clan-levels-modifiers-entity.js create mode 100644 lib/teams/server/entities/clan-members-entity.js create mode 100644 lib/teams/server/models/objection-js/clan-levels-model.js create mode 100644 lib/teams/server/models/objection-js/clan-levels-modifiers-model.js create mode 100644 lib/teams/server/models/objection-js/clan-members-model.js create mode 100644 lib/teams/server/models/objection-js/clan-model.js create mode 100644 lib/teams/server/models/objection-js/registered-models-objection-js.js diff --git a/.env.dist b/.env.dist index abb36d294..0634ce834 100644 --- a/.env.dist +++ b/.env.dist @@ -53,6 +53,9 @@ RELDENS_MAILER_FROM=youremail@gmail.com # RELDENS_ON_BUNDLE_COPY_ASSETS=1 # Use Express to serve static files already bundled. RELDENS_EXPRESS_SERVE_STATICS=1 +# Game Server: +RELDENS_PING_INTERVAL=5000 +RELDENS_PING_MAX_RETRIES=3 # Admin: RELDENS_ADMIN_SECURE_LOGIN=1 RELDENS_ADMIN_ROUTE_PATH="/reldens-admin" diff --git a/lib/game/client/scene-preloader.js b/lib/game/client/scene-preloader.js index 050dbdedf..73cb00a18 100644 --- a/lib/game/client/scene-preloader.js +++ b/lib/game/client/scene-preloader.js @@ -294,7 +294,7 @@ class ScenePreloader extends Scene preloadPlayerDefaultSprite() { - let fallbackImage = this.gameManager.config.get('client/players/animations/fallbackImage') || 'player-base'; + let fallbackImage = this.gameManager.config.get('client/players/animations/fallbackImage', 'player-base'); this.load.spritesheet( GameConst.IMAGE_PLAYER, 'assets/custom/sprites/'+fallbackImage+'.png', diff --git a/lib/game/constants.js b/lib/game/constants.js index 0054cfe1c..76b607aeb 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -2,9 +2,6 @@ * * Reldens - game/constants * - * Project constants and default data. - * Here we use shortcuts since these are used for all the communications between server and client. - * */ module.exports.GameConst = { @@ -28,7 +25,6 @@ module.exports.GameConst = { UI_CLOSE: '-close', UI_OPEN: '-open', CANVAS: 'CANVAS', - // movement: UP: 'up', LEFT: 'left', DOWN: 'down', @@ -36,7 +32,6 @@ module.exports.GameConst = { STOP: 'stop', POINTER: 'mp', ARROW_DOWN: 'ard', - // default image key: IMAGE_PLAYER: 'player', STATUS: { ACTIVE: 1, @@ -44,7 +39,7 @@ module.exports.GameConst = { DEATH: 3, AVOID_INTERPOLATION: 4 }, - THEMES: { + STRUCTURE: { DEFAULT: 'default', ASSETS: 'assets', CSS: 'css', diff --git a/lib/game/server/app-server.js b/lib/game/server/app-server-factory.js similarity index 81% rename from lib/game/server/app-server.js rename to lib/game/server/app-server-factory.js index da05b34e8..0fc9e7942 100644 --- a/lib/game/server/app-server.js +++ b/lib/game/server/app-server-factory.js @@ -1,6 +1,6 @@ /** * - * Reldens - AppServer + * Reldens - AppServerFactory * */ @@ -10,19 +10,15 @@ const fs = require('fs'); const express = require('express'); const cors = require('cors'); -class AppServer +class AppServerFactory { - createAppServer(distPath) + createAppServer() { let appServer = false; let app = express(); app.use(cors()); app.use(express.json()); - if(process.env.RELDENS_EXPRESS_SERVE_STATICS){ - // automatically serve dist files: - app.use('/', express.static(distPath)); - } // if https is not running then by default we will run on http: appServer = process.env.RELDENS_EXPRESS_USE_HTTPS ? this.createHttpsServer(appServer, app) @@ -46,6 +42,11 @@ class AppServer return https.createServer(credentials, app); } + enableServeStatics(app, distPath) + { + app.use('/', express.static(distPath)); + } + } -module.exports.AppServer = new AppServer(); +module.exports.AppServerFactory = new AppServerFactory(); diff --git a/lib/game/server/manager.js b/lib/game/server/manager.js index f6df97bfb..ff11202ec 100644 --- a/lib/game/server/manager.js +++ b/lib/game/server/manager.js @@ -9,8 +9,9 @@ const dotenv = require('dotenv'); const path = require('path'); +const ReldensASCII = require('../reldens-ascii'); const { GameServer } = require('./game-server'); -const { AppServer } = require('./app-server'); +const { AppServerFactory } = require('./app-server-factory'); const { ConfigManager } = require('../../config/server/manager'); const { DataServerInitializer } = require('./data-server-initializer'); const { FeaturesManager } = require('../../features/server/manager'); @@ -21,7 +22,6 @@ const { Mailer } = require('./mailer'); const { ThemeManager } = require('./theme-manager'); const { MapsLoader } = require('./maps-loader'); const { ForgotPassword } = require('./forgot-password'); -const ReldensASCII = require('../reldens-ascii'); const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils'); const { WebSocketTransport } = require('@colyseus/ws-transport'); @@ -49,21 +49,16 @@ class ServerManager { this.events = eventsManager || EventsManagerSingleton; try { - // custom plugin goes even before config to catch up every single event: + // @NOTE: custom plugin goes even before config to catch up on every single event. this.setupCustomServerPlugin(config); - // initialize configurations: this.initializeConfiguration(config); - // initialize theme: this.themeManager = new ThemeManager(config); this.themeManager.validateOrCreateTheme(); - // initialize storage: this.initializeStorage(config, dataServerDriver).catch((error) => { - Logger.error('Storage could not be initialized.', error); + Logger.critical('Storage could not be initialized.', error); process.exit(); }); - // set storage driver on configuration manager: this.configManager.dataServer = this.dataServer; - // load maps: MapsLoader.loadMaps(this.themeManager.projectThemePath, this.configManager); } catch (e) { Logger.error('Broken ServerManager.', e.message, e.stack); @@ -72,19 +67,6 @@ class ServerManager } } - async initializeStorage(config, dataServerDriver) - { - let {dataServerConfig, dataServer} = DataServerInitializer.initializeEntitiesAndDriver({ - config, - dataServerDriver, - serverManager: this - }); - this.dataServerConfig = dataServerConfig; - this.dataServer = dataServer; - await dataServer.connect(); // can't auto-connect on the constructor - await dataServer.generateEntities(); - } - setupCustomServerPlugin(config) { let customPluginClass = sc.get(config, 'customPlugin', false); @@ -122,40 +104,86 @@ class ServerManager this.isHotPlugEnabled = process.env.RELDENS_HOT_PLUG || false; } + async initializeStorage(config, dataServerDriver) + { + let {dataServerConfig, dataServer} = DataServerInitializer.initializeEntitiesAndDriver({ + config, + dataServerDriver, + serverManager: this + }); + this.dataServerConfig = dataServerConfig; + this.dataServer = dataServer; + await dataServer.connect(); // can't auto-connect on the constructor + await dataServer.generateEntities(); + } + async start() { - Logger.info('Starting Server Manager!'); - await this.createServer(); - await this.createGameServer(); + Logger.info('Starting Server!'); + if(!this.gameServer){ + Logger.critical('App Server is not defined.'); + return false; + } + if(!this.gameServer){ + Logger.critical('Game Server is not defined.'); + return false; + } await this.initializeManagers(); // after the rooms were loaded then finish the server process: await this.events.emit('reldens.serverBeforeListen', {serverManager: this}); await this.gameServer.listen(this.configServer.port); this.configManager.configList.server.baseUrl = this.configServer.host+':'+this.configServer.port; - await this.createClientBundle(); Logger.info('Server ready.'+ReldensASCII); Logger.info('Server listening on '+this.configServer.host+':'+this.configServer.port); await this.events.emit('reldens.serverReady', {serverManager: this}); } - async createServer() + async createServers() + { + await this.createAppServer(); + this.enableServeStatics(); + await this.createGameServer(); + } + + async createAppServer() { - await this.events.emit('reldens.serverStartBegin', {serverManager: this}); - Object.assign(this, AppServer.createAppServer(this.themeManager.distPath)); + // @TODO - BETA - Pass AppServerFactory to the ServerManager constructor to avoid the other libraries require + // if are not needed. Modify theme/index.js.dist to pass it on the default installation. + let event = {serverManager: this, continueProcess: true}; + await this.events.emit('reldens.createAppServer', event); + if(!event.continueProcess){ + return false; + } + Object.assign(this, AppServerFactory.createAppServer()); + } + + enableServeStatics() + { + if(!process.env.RELDENS_EXPRESS_SERVE_STATICS){ + return false; + } + AppServerFactory.enableServeStatics(this.app, this.themeManager.distPath); } async createGameServer() { - // create game server instance: - // this.gameServer = new GameServer({server: this.appServer, express: this.app}); + // @TODO - BETA - Extract into a GameServerFactory, pass it to the ServerManager constructor to avoid the other + // libraries require if are not needed. Modify theme/index.js.dist to pass it on the default installation. + let options = { + pingInterval: process.env.RELDENS_PING_INTERVAL || 5000, + pingMaxRetries: process.env.RELDENS_PING_MAX_RETRIES || 3 + }; + if(this.appServer){ + options.server = this.appServer; + } + let event = {options, continueProcess: true}; + await this.events.emit('reldens.createGameServer', event); + if(!event.continueProcess){ + return false; + } this.gameServer = new GameServer({ - transport: new WebSocketTransport({ - pingInterval: 5000, - pingMaxRetries: 3, - server: this.appServer - }) + transport: new WebSocketTransport(options) }); - // attach web monitoring panel (optional): if(this.configServer.monitor.enabled){ this.gameServer.attachMonitor(this.app, this.configServer.monitor); } @@ -171,7 +199,7 @@ class ServerManager serverManager: this, configProcessor: this.configManager }); - // mailer: + // @TODO - BETA - Extract and pass to the ServerManager in the constructor. this.mailer = new Mailer(); Logger.info(['Mailer Configured:', this.mailer.isEnabled()]); await ForgotPassword.defineRequestOnServerManagerApp(this); @@ -206,23 +234,6 @@ class ServerManager }); } - async createClientBundle() - { - // @TODO - BETA - Remove this function, just move to an auto-install on first run feature. - let runBundler = process.env.RELDENS_PARCEL_RUN_BUNDLER || false; - if(!runBundler){ - return false; - } - if(process.env.RELDENS_ON_BUNDLE_RESET_DIST){ - await this.themeManager.resetDist(); - } - if(process.env.RELDENS_ON_BUNDLE_RESET_DIST || process.env.RELDENS_ON_BUNDLE_COPY_ASSETS){ - await this.themeManager.copyAssetsToDist(); - } - Logger.info('Running bundle on: ' + this.themeManager.projectIndexPath); - await this.themeManager.buildClient(); - } - } module.exports.ServerManager = ServerManager; diff --git a/lib/game/server/theme-manager.js b/lib/game/server/theme-manager.js index 988d125b7..ffd971fd0 100644 --- a/lib/game/server/theme-manager.js +++ b/lib/game/server/theme-manager.js @@ -27,7 +27,7 @@ class ThemeManager assetsDistPath = ''; cssDistPath = ''; themePath = ''; - projectThemeName = GameConst.THEMES.DEFAULT; + projectThemeName = GameConst.STRUCTURE.DEFAULT; projectThemePath = ''; projectPluginsPath = ''; projectAssetsPath = ''; @@ -46,22 +46,22 @@ class ThemeManager { this.projectRoot = props.projectRoot; this.projectRootPackageJson = path.join(this.projectRoot, 'package.json'); - this.projectThemeName = sc.get(props, 'projectThemeName', GameConst.THEMES.DEFAULT); + this.projectThemeName = sc.get(props, 'projectThemeName', GameConst.STRUCTURE.DEFAULT); this.reldensModulePath = path.join(this.projectRoot, 'node_modules', 'reldens'); - this.reldensModuleLibPath = path.join(this.reldensModulePath, GameConst.THEMES.LIB); - this.reldensModuleThemePath = path.join(this.reldensModulePath, GameConst.THEMES.THEME); - this.reldensModuleDefaultThemePath = path.join(this.reldensModuleThemePath, GameConst.THEMES.DEFAULT); - this.reldensModuleDefaultThemeAssetsPath = path.join(this.reldensModuleDefaultThemePath, GameConst.THEMES.ASSETS); - this.reldensModuleThemePluginsPath = path.join(this.reldensModuleThemePath, GameConst.THEMES.PLUGINS); - this.distPath = path.join(this.projectRoot, GameConst.THEMES.DIST); - this.assetsDistPath = path.join(this.distPath, GameConst.THEMES.ASSETS); - this.cssDistPath = path.join(this.distPath, GameConst.THEMES.CSS); - this.themePath = path.join(this.projectRoot, GameConst.THEMES.THEME); + this.reldensModuleLibPath = path.join(this.reldensModulePath, GameConst.STRUCTURE.LIB); + this.reldensModuleThemePath = path.join(this.reldensModulePath, GameConst.STRUCTURE.THEME); + this.reldensModuleDefaultThemePath = path.join(this.reldensModuleThemePath, GameConst.STRUCTURE.DEFAULT); + this.reldensModuleDefaultThemeAssetsPath = path.join(this.reldensModuleDefaultThemePath, GameConst.STRUCTURE.ASSETS); + this.reldensModuleThemePluginsPath = path.join(this.reldensModuleThemePath, GameConst.STRUCTURE.PLUGINS); + this.distPath = path.join(this.projectRoot, GameConst.STRUCTURE.DIST); + this.assetsDistPath = path.join(this.distPath, GameConst.STRUCTURE.ASSETS); + this.cssDistPath = path.join(this.distPath, GameConst.STRUCTURE.CSS); + this.themePath = path.join(this.projectRoot, GameConst.STRUCTURE.THEME); this.projectThemePath = path.join(this.themePath, this.projectThemeName); - this.projectPluginsPath = path.join(this.themePath, GameConst.THEMES.PLUGINS); - this.projectAssetsPath = path.join(this.projectThemePath, GameConst.THEMES.ASSETS); - this.projectCssPath = path.join(this.projectThemePath, GameConst.THEMES.CSS); - this.projectIndexPath = path.join(this.projectThemePath, GameConst.THEMES.INDEX); + this.projectPluginsPath = path.join(this.themePath, GameConst.STRUCTURE.PLUGINS); + this.projectAssetsPath = path.join(this.projectThemePath, GameConst.STRUCTURE.ASSETS); + this.projectCssPath = path.join(this.projectThemePath, GameConst.STRUCTURE.CSS); + this.projectIndexPath = path.join(this.projectThemePath, GameConst.STRUCTURE.INDEX); } paths() @@ -205,7 +205,7 @@ class ThemeManager async buildCss() { - let themeScss = path.join(this.projectCssPath, GameConst.THEMES.SCSS_FILE).toString(); + let themeScss = path.join(this.projectCssPath, GameConst.STRUCTURE.SCSS_FILE).toString(); let bundler = this.createCssBundler(themeScss); try { let { buildTime } = await bundler.run(); @@ -218,9 +218,9 @@ class ThemeManager async buildAdminCss() { - let adminCss = path.join(this.projectCssPath, GameConst.THEMES.ADMIN_CSS_FILE); + let adminCss = path.join(this.projectCssPath, GameConst.STRUCTURE.ADMIN_CSS_FILE); if(!fs.existsSync(adminCss)){ - let adminScss = path.join(this.projectCssPath, GameConst.THEMES.ADMIN_SCSS_FILE); + let adminScss = path.join(this.projectCssPath, GameConst.STRUCTURE.ADMIN_SCSS_FILE); let bundler = this.createCssBundler(adminScss); try { let { buildTime } = await bundler.run(); @@ -230,7 +230,7 @@ class ThemeManager ErrorManager.error('Parcel build Game Client process failed.'); } } - let adminCssDist = path.join(this.cssDistPath, GameConst.THEMES.ADMIN_CSS_FILE); + let adminCssDist = path.join(this.cssDistPath, GameConst.STRUCTURE.ADMIN_CSS_FILE); if(!fs.existsSync(this.cssDistPath)){ fs.mkdirSync(this.cssDistPath); } @@ -360,6 +360,23 @@ class ThemeManager return TemplateEngine.render(fileContent, params); } + async createClientBundle() + { + // @TODO - BETA - Remove this function, just move to an auto-installation on first run feature. + let runBundler = process.env.RELDENS_PARCEL_RUN_BUNDLER || false; + if(!runBundler){ + return false; + } + if(process.env.RELDENS_ON_BUNDLE_RESET_DIST){ + await this.resetDist(); + } + if(process.env.RELDENS_ON_BUNDLE_RESET_DIST || process.env.RELDENS_ON_BUNDLE_COPY_ASSETS){ + await this.copyAssetsToDist(); + } + Logger.info('Running bundle on: ' + this.projectIndexPath); + await this.buildClient(); + } + } module.exports.ThemeManager = ThemeManager; diff --git a/lib/inventory/client/plugin.js b/lib/inventory/client/plugin.js index fba00eb94..b24925759 100644 --- a/lib/inventory/client/plugin.js +++ b/lib/inventory/client/plugin.js @@ -22,10 +22,6 @@ class InventoryPlugin extends PluginInterface { // @TODO - BETA - Refactor plugin, extract all the methods into new classes. this.gameManager = sc.get(props, 'gameManager', false); - this.tradeTargetAction = new TradeTargetAction(); - // @TODO - BETA - Make the dialogBox template load on it's own so we can use the same object from cache everytime. - // @NOTE: the tradeUi works as preload for the trade template which at the end is an dialog-box. - this.tradeUi = new UserInterface(this.gameManager, {id: 'trade', type: 'trade'}); if(!this.gameManager){ Logger.error('Game Manager undefined in InventoryPlugin.'); } @@ -33,6 +29,10 @@ class InventoryPlugin extends PluginInterface if(!this.events){ Logger.error('EventsManager undefined in InventoryPlugin.'); } + this.tradeTargetAction = new TradeTargetAction(); + // @TODO - BETA - Make the dialogBox template load on it's own so we can reuse the same object from cache. + // @NOTE: the tradeUi works as preload for the trade template which at the end is an dialog-box. + this.tradeUi = new UserInterface(this.gameManager, {id: 'trade', type: 'trade'}); this.events.on('reldens.playersOnAdd', (player, key, previousScene, roomEvents) => { this.onPlayerAdd(key, roomEvents, player); }); @@ -98,7 +98,6 @@ class InventoryPlugin extends PluginInterface if(!roomEvents.gameManager.inventory){ this.createInventoryInstance(player, roomEvents); } - // listen to room messages: roomEvents.room.onMessage('*', (message) => { roomEvents.gameManager.inventory.processMessage(message); }); @@ -291,7 +290,6 @@ class InventoryPlugin extends PluginInterface details.style.display = 'block'; } }); - // show item trash: let buttonElement = inventoryPanel.querySelector('#item-trash-' + idx + ' img'); if(!buttonElement){ Logger.error(['Missing button.', buttonElement]); @@ -310,15 +308,19 @@ class InventoryPlugin extends PluginInterface }; preloadScene.gameManager.room.send('*', optionSend); }); - // use: if(this.isUsable(item)){ let useBtn = domMan.getElement('#item-use-'+idx); - useBtn.addEventListener('click', this.clickedBox.bind(this, idx, InventoryConst.ACTION_USE, preloadScene)); + useBtn.addEventListener( + 'click', + this.clickedBox.bind(this, idx, InventoryConst.ACTION_USE, preloadScene) + ); } - // equip / unequip: if(this.isEquipment(item)){ let equipBtn = domMan.getElement('#item-equip-'+idx); - equipBtn.addEventListener('click', this.clickedBox.bind(this, idx, InventoryConst.ACTION_EQUIP, preloadScene)); + equipBtn.addEventListener( + 'click', + this.clickedBox.bind(this, idx, InventoryConst.ACTION_EQUIP, preloadScene) + ); } } diff --git a/lib/teams/client/clan-initializer-handler.js b/lib/teams/client/clan-initializer-handler.js new file mode 100644 index 000000000..4b20c95a2 --- /dev/null +++ b/lib/teams/client/clan-initializer-handler.js @@ -0,0 +1,27 @@ +/** + * + * Reldens - ClanInitializerHandler + * + */ + +const { sc } = require('@reldens/utils'); + +class ClanInitializerHandler +{ + + static appendCreateUiListener(initialGameData, teamsPlugin) + { + let clanData = sc.get(initialGameData, 'clanData', {}); + if(0 === Object.keys(clanData).length){ + return false; + } + teamsPlugin.events.on('reldens.createUiScene', (preloadScene) => { + // this.clanUi = new ClanUi(preloadScene); + // this.clanUi.createUi(); + }); + + } + +} + +module.exports.ClanInitializerHandler = ClanInitializerHandler; diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index d3057a349..7ad6a08dd 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -8,6 +8,7 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); const { TemplatesHandler } = require('./templates-handler'); +const { ClanInitializerHandler } = require('./clan-initializer-handler'); const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); @@ -30,6 +31,9 @@ class TeamsPlugin extends PluginInterface this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { TeamTargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); }); + this.events.on('reldens.beforeCreateEngine', (initialGameData) => { + ClanInitializerHandler.appendCreateUiListener(initialGameData, this); + }); this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 5a9833d85..4603b89cc 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -5,18 +5,25 @@ */ let pref = 'tm.' +let clanPref = 'cl.'; module.exports.TeamsConst = { KEY: 'teams', CLAN_KEY: 'clan', TEAM_PREF: pref, + CLAN_PREF: clanPref, ACTIONS: { + // @TODO - BETA - Standardize generic actions and use dots to split, like UPDATE = '.up', REMOVE = '.rm', etc. TEAM_INVITE: pref+'inv', TEAM_ACCEPTED: pref+'acp', TEAM_LEAVE: pref+'lev', TEAM_UPDATE: pref+'upd', TEAM_LEFT: pref+'lef', TEAM_REMOVE: pref+'rem', + CLAN_INVITE: clanPref+'inv', + CLAN_ACCEPTED: clanPref+'acp', + CLAN_LEAVE: clanPref+'lev', + CLAN_UPDATE: clanPref+'upd', }, LABELS: { INVITE_BUTTON_LABEL: 'Team - Invite', diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js new file mode 100644 index 000000000..4ea764bd7 --- /dev/null +++ b/lib/teams/server/clan.js @@ -0,0 +1,42 @@ +/** + * + * Reldens - Clan + * + */ + +const { Team } = require('./team'); +const { sc } = require('@reldens/utils'); + +class Clan extends Team +{ + constructor(props) + { + super(props); + this.name = sc.get(props, 'name', ''); + this.points = sc.get(props, 'points', ''); + } + + static fromModel(clanModel, owner, sharedProperties) + { + return new this({ + owner, + sharedProperties, + ownerId: clanModel.owner_id, + name: clanModel.name, + points: clanModel.points, + level: clanModel.level + }); + } + + forSendData() + { + return { + name: this.name, + points: this.points, + level: this.level + }; + } + +} + +module.exports.Clan = Clan; \ No newline at end of file diff --git a/lib/teams/server/entities-config.js b/lib/teams/server/entities-config.js new file mode 100644 index 000000000..49d6cbd17 --- /dev/null +++ b/lib/teams/server/entities-config.js @@ -0,0 +1,24 @@ +/** + * + * Reldens - Entities Config + * + */ + +const { ClanEntity } = require('./entities/clan-entity'); +const { ClanLevelsEntity } = require('./entities/clan-levels-entity'); +const { ClanLevelsModifiersEntity } = require('./entities/clan-levels-modifiers-entity'); +const { ClanMembersEntity } = require('./entities/clan-members-entity'); + +let objectsConfig = { + parentItemLabel: 'Clan', + icon: 'User' +}; + +let entitiesConfig = { + clan: ClanEntity.propertiesConfig(objectsConfig), + clanLevels: ClanLevelsEntity.propertiesConfig(objectsConfig), + clanLevelsModifiers: ClanLevelsModifiersEntity.propertiesConfig(objectsConfig), + clanMembers: ClanMembersEntity.propertiesConfig(objectsConfig) +}; + +module.exports.entitiesConfig = entitiesConfig; diff --git a/lib/teams/server/entities-translations.js b/lib/teams/server/entities-translations.js new file mode 100644 index 000000000..af88e0162 --- /dev/null +++ b/lib/teams/server/entities-translations.js @@ -0,0 +1,14 @@ +/** + * + * Reldens - Entities Translations + * + */ + +module.exports.entitiesTranslations = { + labels: { + clan: 'Clan', + clan_levels: 'Levels', + clan_levels_modifiers: 'Levels Modifiers', + clan_members: 'Members' + } +}; diff --git a/lib/teams/server/entities/clan-entity.js b/lib/teams/server/entities/clan-entity.js new file mode 100644 index 000000000..f7ed95642 --- /dev/null +++ b/lib/teams/server/entities/clan-entity.js @@ -0,0 +1,45 @@ +/** + * + * Reldens - ClanEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); + +class ClanEntity extends EntityProperties +{ + + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + owner_id: { + type: 'reference', + reference: 'players', + isRequired: true + }, + name: { + isRequired: true + }, + points: { + type: 'number' + }, + level: {} + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.ClanEntity = ClanEntity; diff --git a/lib/teams/server/entities/clan-levels-entity.js b/lib/teams/server/entities/clan-levels-entity.js new file mode 100644 index 000000000..841f4120a --- /dev/null +++ b/lib/teams/server/entities/clan-levels-entity.js @@ -0,0 +1,44 @@ +/** + * + * Reldens - ClanLevelsEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); + +class ClanLevelsEntity extends EntityProperties +{ + + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + key: { + type: 'number', + isRequired: true + }, + label: { + isRequired: true + }, + required_experience: { + type: 'number', + isRequired: true + } + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.ClanLevelsEntity = ClanLevelsEntity; diff --git a/lib/teams/server/entities/clan-levels-modifiers-entity.js b/lib/teams/server/entities/clan-levels-modifiers-entity.js new file mode 100644 index 000000000..fd652cb0d --- /dev/null +++ b/lib/teams/server/entities/clan-levels-modifiers-entity.js @@ -0,0 +1,75 @@ +/** + * + * Reldens - ClanLevelsModifiersEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); +const { sc } = require('@reldens/utils'); + +class ClanLevelsModifiersEntity extends EntityProperties +{ + + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + level_id: { + type: 'reference', + reference: 'clan_levels', + isRequired: true + }, + key: { + isTitle: true, + isRequired: true + }, + property_key: { + isRequired: true + }, + operation: { + availableValues: [ + {value: 1, label: 'Increment'}, + {value: 2, label: 'Decrease'}, + {value: 3, label: 'Divide'}, + {value: 4, label: 'Multiply'}, + {value: 5, label: 'Increment Percentage'}, + {value: 6, label: 'Decrease Percentage'}, + {value: 7, label: 'Set'}, + {value: 8, label: 'Method'}, + {value: 9, label: 'Set Number'} + ], + isRequired: true + }, + value: { + isRequired: true + }, + minValue: {}, + maxValue: {}, + minProperty: {}, + maxProperty: {}, + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + + listPropertiesKeys = sc.removeFromArray(listPropertiesKeys, [ + 'minValue', + 'maxValue', + 'minProperty', + 'maxProperty' + ]); + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return { + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties, + ...extraProps + }; + } + +} + +module.exports.ClanLevelsModifiersEntity = ClanLevelsModifiersEntity; diff --git a/lib/teams/server/entities/clan-members-entity.js b/lib/teams/server/entities/clan-members-entity.js new file mode 100644 index 000000000..336f7ebc1 --- /dev/null +++ b/lib/teams/server/entities/clan-members-entity.js @@ -0,0 +1,43 @@ +/** + * + * Reldens - ClanMembersEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); + +class ClanMembersEntity extends EntityProperties +{ + + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + clan_id: { + type: 'reference', + reference: 'clan', + isRequired: true + }, + player_id: { + type: 'reference', + reference: 'players', + isRequired: true + }, + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.ClanMembersEntity = ClanMembersEntity; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 643ee67e9..abce458b8 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -4,40 +4,43 @@ * */ +const { Clan } = require('../clan'); +const { TeamsConst } = require('../../constants'); + class CreatePlayerClanHandler { - static async enrichPlayerWithClan(client, playerSchema, room, events, modelsManager) + static async enrichPlayerWithClan(client, playerSchema, room, teamsPlugin) { - /* - let serverProps = { - owner: currentPlayer, - client: new ClientWrapper({client, room}), - persistence: true, - ownerIdProperty: 'player_id', - eventsManager: events, - modelsManager: modelsManager, - itemClasses: room.config.getWithoutLogs('server/customClasses/inventory/items', {}), - groupClasses: room.config.getWithoutLogs('server/customClasses/inventory/groups', {}), - itemsModelData: room.config.inventory.items - }; - let inventoryServer = new ItemsServer(serverProps); - // broadcast player sessionId to share animations: - inventoryServer.client.sendTargetProps.broadcast.push('sessionId'); - // load all the items here and then create instances for later use: - await inventoryServer.dataServer.loadOwnerItems(); - // create player inventory: - currentPlayer.inventory = inventoryServer; - // @NOTE: here we send the groups data to generate the player interface instead of set them in the current - // player inventory because for this specific implementation we don't need recursive groups lists in the - // server for each player. + let startEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; + teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClan', startEvent); + if(!startEvent.continueProcess){ + return false; + } + let clanModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneByWithRelations( + 'player_id', + playerSchema.player_id, + ['parent_clan.parent_level.modifiers'] + ); + if(!clanModel){ + return false; + } + let clan = Clan.fromModel( + clanModel.parent_clan, + playerSchema, + room.config.get('client/ui/teams/sharedProperties') + ); + playerSchema.privateData.clan = clan; let sendData = { - act: ItemsConst.ACTION_SET_GROUPS, - owner: currentPlayer.inventory.manager.getOwnerId(), - groups: room.config.get('inventory/groups/groupBaseData') + act: TeamsConst.ACTIONS.CLAN_UPDATE, + clan: clan.forSendData(), }; + let endEvent = {client, playerSchema, room, teamsPlugin, sendData, continueProcess: true}; + teamsPlugin.events.emit('reldens.beforePlayerClanDataSend', endEvent); + if(!endEvent.continueProcess){ + return false; + } client.send('*', sendData); - */ } } diff --git a/lib/teams/server/event-handlers/create-player-team-handler.js b/lib/teams/server/event-handlers/create-player-team-handler.js index 39c129d66..c6ca81ccf 100644 --- a/lib/teams/server/event-handlers/create-player-team-handler.js +++ b/lib/teams/server/event-handlers/create-player-team-handler.js @@ -10,7 +10,7 @@ const { Logger } = require('@reldens/utils'); class CreatePlayerTeamHandler { - static async joinExistentTeam(client, playerSchema, room, events, modelsManager, teamsPlugin) + static async joinExistentTeam(client, playerSchema, teamsPlugin) { let teamId = teamsPlugin.changingRoomPlayers[playerSchema.player_id]?.teamId; if(!teamId){ diff --git a/lib/teams/server/event-handlers/stats-update-team-handler.js b/lib/teams/server/event-handlers/stats-update-team-handler.js index a2f68c4a3..4d8484b30 100644 --- a/lib/teams/server/event-handlers/stats-update-team-handler.js +++ b/lib/teams/server/event-handlers/stats-update-team-handler.js @@ -15,7 +15,6 @@ class StatsUpdateTeamHandler let {teamsPlugin, playerSchema} = props; let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); if('' === currentTeamId){ - Logger.info('Team ID not present.', currentTeamId, playerSchema.currentTeam); return; } let currentTeam = sc.get(teamsPlugin.teams, currentTeamId, false); diff --git a/lib/teams/server/models/objection-js/clan-levels-model.js b/lib/teams/server/models/objection-js/clan-levels-model.js new file mode 100644 index 000000000..f700a9419 --- /dev/null +++ b/lib/teams/server/models/objection-js/clan-levels-model.js @@ -0,0 +1,35 @@ +/** + * + * Reldens - ClanLevelsModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); +const {PlayersModel} = require("../../../../users/server/models/objection-js/players-model"); + +class ClanLevelsModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'clan_levels'; + } + + static get relationMappings() + { + let { ClanLevelsModifiersModel } = require('./clan-levels-modifiers-model'); + return { + modifiers: { + relation: this.HasManyRelation, + modelClass: ClanLevelsModifiersModel, + join: { + from: this.tableName+'.id', + to: ClanLevelsModifiersModel.tableName+'.level_id' + } + } + } + } + +} + +module.exports.ClanLevelsModel = ClanLevelsModel; diff --git a/lib/teams/server/models/objection-js/clan-levels-modifiers-model.js b/lib/teams/server/models/objection-js/clan-levels-modifiers-model.js new file mode 100644 index 000000000..df49325a8 --- /dev/null +++ b/lib/teams/server/models/objection-js/clan-levels-modifiers-model.js @@ -0,0 +1,34 @@ +/** + * + * Reldens - ClanLevelsModifiersModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); + +class ClanLevelsModifiersModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'clan_levels_modifiers'; + } + + static get relationMappings() + { + const { ClanLevelsModel } = require('./clan-levels-model'); + return { + parent_level: { + relation: this.HasOneRelation, + modelClass: ClanLevelsModel, + join: { + from: this.tableName+'.level_id', + to: ClanLevelsModel.tableName+'.id' + } + } + } + } + +} + +module.exports.ClanLevelsModifiersModel = ClanLevelsModifiersModel; diff --git a/lib/teams/server/models/objection-js/clan-members-model.js b/lib/teams/server/models/objection-js/clan-members-model.js new file mode 100644 index 000000000..e1055d249 --- /dev/null +++ b/lib/teams/server/models/objection-js/clan-members-model.js @@ -0,0 +1,43 @@ +/** + * + * Reldens - ClanMembersModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); + +class ClanMembersModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'clan_members'; + } + + static get relationMappings() + { + let { ClanModel } = require('./clan-model'); + let { PlayersModel } = require('../../../../users/server/models/objection-js/players-model'); + return { + parent_clan: { + relation: this.HasOneRelation, + modelClass: ClanModel, + join: { + from: this.tableName+'.clan_id', + to: ClanModel.tableName+'.id' + } + }, + parent_player: { + relation: this.HasManyRelation, + modelClass: PlayersModel, + join: { + from: this.tableName+'.id', + to: PlayersModel.tableName+'.player_id' + } + } + } + } + +} + +module.exports.ClanMembersModel = ClanMembersModel; diff --git a/lib/teams/server/models/objection-js/clan-model.js b/lib/teams/server/models/objection-js/clan-model.js new file mode 100644 index 000000000..6dd5119d2 --- /dev/null +++ b/lib/teams/server/models/objection-js/clan-model.js @@ -0,0 +1,44 @@ +/** + * + * Reldens - ClanModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); +const {PlayersModel} = require("../../../../users/server/models/objection-js/players-model"); + +class ClanModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'clan'; + } + + static get relationMappings() + { + let { PlayersModel } = require('../../../../users/server/models/objection-js/players-model'); + let { ClanLevelsModel } = require('./clan-levels-model'); + return { + player_owner: { + relation: this.HasOneRelation, + modelClass: PlayersModel, + join: { + from: this.tableName+'.owner_id', + to: PlayersModel.tableName+'.id' + } + }, + parent_level: { + relation: this.HasOneRelation, + modelClass: ClanLevelsModel, + join: { + from: this.tableName+'.level', + to: ClanLevelsModel.tableName+'.key' + } + } + } + } + +} + +module.exports.ClanModel = ClanModel; diff --git a/lib/teams/server/models/objection-js/registered-models-objection-js.js b/lib/teams/server/models/objection-js/registered-models-objection-js.js new file mode 100644 index 000000000..ec15b1b65 --- /dev/null +++ b/lib/teams/server/models/objection-js/registered-models-objection-js.js @@ -0,0 +1,26 @@ +/** + * + * Reldens - Registered Models + * + */ + + +const { ClanModel } = require('../../models/objection-js/clan-model'); +const { ClanLevelsModel } = require('../../models/objection-js/clan-levels-model'); +const { ClanLevelsModifiersModel } = require('../../models/objection-js/clan-levels-modifiers-model'); +const { ClanMembersModel } = require('../../models/objection-js/clan-members-model'); +const { entitiesConfig } = require('../../entities-config'); +const { entitiesTranslations } = require('../../entities-translations'); + +let rawRegisteredEntities = { + clan: ClanModel, + clanLevels: ClanLevelsModel, + clanLevelsModifiers: ClanLevelsModifiersModel, + clanMembers: ClanMembersModel +}; + +module.exports.rawRegisteredEntities = rawRegisteredEntities; + +module.exports.entitiesConfig = entitiesConfig; + +module.exports.entitiesTranslations = entitiesTranslations; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index d4e1e7002..5932907b9 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -21,28 +21,18 @@ class TeamsPlugin extends PluginInterface if(!this.events){ Logger.error('EventsManager undefined in TeamsPlugin.'); } + this.dataServer = sc.get(props, 'dataServer', false); + if(!this.dataServer){ + Logger.error('DataServer undefined in TeamsPlugin.'); + } this.teams = sc.get(props, 'teams', {}); this.changingRoomPlayers = sc.get(props, 'changingRoomPlayers', {}); this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { roomMessageActions.teams = new TeamsMessageActions({teamsPlugin: this}); }); this.events.on('reldens.createPlayerAfter', async (client, authResult, playerSchema, room) => { - await CreatePlayerClanHandler.enrichPlayerWithClan( - client, - playerSchema, - room, - this.events, - this.modelsManager, - this - ); - await CreatePlayerTeamHandler.joinExistentTeam( - client, - playerSchema, - room, - this.events, - this.modelsManager, - this - ); + await CreatePlayerClanHandler.enrichPlayerWithClan(client, playerSchema, room, this); + await CreatePlayerTeamHandler.joinExistentTeam(client, playerSchema, this); }); this.events.on('reldens.savePlayerStatsUpdateClient', async (client, playerSchema, room) => { await StatsUpdateTeamHandler.updateTeam({teamsPlugin: this, playerSchema}); diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index 39bf16798..05f24b68c 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -8,6 +8,7 @@ const { ErrorManager, sc } = require('@reldens/utils'); class Team { + constructor(props) { this.level = sc.get(props, 'level', 1); diff --git a/lib/users/server/player.js b/lib/users/server/player.js index ac0edf33f..8805c19fb 100644 --- a/lib/users/server/player.js +++ b/lib/users/server/player.js @@ -20,9 +20,9 @@ class Player extends Schema super(); try { let player = data.player; - // player data: - this.id = data.id; // this is the user id - this.player_id = player.id; + this.id = data.id; // this is the user_id from the storage + // @TODO - BETA - Use camelCase on player_id and role_id. + this.player_id = player.id; // this is the player_id from the storage this.sessionId = sessionId; this.broadcastKey = sessionId; this.role_id = data.role_id; @@ -33,12 +33,17 @@ class Player extends Schema this.eventsPrefix = 'p'+player.id+'.'+this.sessionId; // body state contains the scene and position data: this.state = new BodyState(player.state); - // stats for now will use the stats model. + // @NOTE: stats can't be a schema because is private data manually handled. this.stats = player.stats; // this is the current value this.statsBase = player.statsBase; // this is the base or max value this.avatarKey = GameConst.IMAGE_PLAYER; this.inState = GameConst.STATUS.ACTIVE; this.playedTime = data.played_time; + // @TODO - BETA - Look for references where we assign data on the player using dynamic properties and move + // the data inside this privateData property. For example: playerSchema.currentAction, actions, inventory, + // skillsServer, physicalBody, etc. should become playerSchema.privateData.currentAction. + this.privateData = {}; + this.customData = {}; } catch (err) { ErrorManager.error(['Player creation missing data.', err]); } diff --git a/theme/index.js.dist b/theme/index.js.dist index d707f75b2..e43630ce6 100644 --- a/theme/index.js.dist +++ b/theme/index.js.dist @@ -20,6 +20,7 @@ let appServer = new ServerManager({ projectThemeName: 'custom-game-theme-test', // if the project theme is not specified then "default" will be used customPlugin: ServerPlugin }); + console.log('TEST - All these are TEST logs that you can remove from your index file.'); // events debug: @@ -29,7 +30,7 @@ appServer.events.on('reldens.serverConfigFeaturesReady', (props) => { console.log('TEST - Events test reldens.serverConfigFeaturesReady success!'); }); -// blocked admin actions test: +// blocked admin actions test (remove to enable the admin actions): if('1' === process.env.RELDENS_BLOCKED_ADMIN){ appServer.events.on('reldens.beforeCreateAdminManager', (adminPack, dispatchedEvent) => { for(let adminResource of dispatchedEvent.serverManager.dataServer.resources){ @@ -50,9 +51,12 @@ if('1' === process.env.RELDENS_BLOCKED_ADMIN){ // run the server! console.log('TEST - ServerPlugin starting...'); -appServer.start().then(() => { - console.log('TEST - SERVER UP AND RUNNING!'); -}).catch((err) => { - console.log('TEST - ServerPlugin error:', err); - process.exit(); +appServer.createServers().then(() => { + console.log('TEST - CREATED APP SERVER AND GAME SERVER INSTANCES!'); + appServer.start().then(() => { + console.log('TEST - SERVER UP AND RUNNING!'); + }).catch((err) => { + console.log('TEST - ServerPlugin error:', err); + process.exit(); + }); }); diff --git a/theme/plugins/server-plugin.js b/theme/plugins/server-plugin.js index 550b980f7..776cd94bf 100644 --- a/theme/plugins/server-plugin.js +++ b/theme/plugins/server-plugin.js @@ -21,7 +21,7 @@ class ServerPlugin extends PluginInterface setup(props) { this.events = props.events; - this.events.on('reldens.serverStartBegin', (props) => { + this.events.on('reldens.createAppServer', (props) => { this.defineCustomClasses(props); }); } From 6337114b28fd540e76ab390d94854b694d3393e6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 12 Mar 2023 20:32:24 +0100 Subject: [PATCH 26/82] - Reldens - v4.0.0 - Client extractions for clans. - Clans templates. --- lib/teams/client/clan-initializer-handler.js | 2 + lib/teams/client/plugin.js | 7 ++- lib/teams/client/team-message-handler.js | 10 ++-- lib/teams/client/team-target-box-enricher.js | 52 ++++++++++++---- lib/teams/client/templates-handler.js | 6 ++ lib/teams/constants.js | 24 ++++++-- lib/teams/server/clan-members-data-mapper.js | 27 +++++++++ lib/teams/server/clan.js | 9 ++- .../create-player-clan-handler.js | 24 ++++++-- .../models/objection-js/clan-members-model.js | 6 +- .../server/models/objection-js/clan-model.js | 11 +++- lib/teams/server/players-data-mapper.js | 46 +++++++++++++++ lib/teams/server/plugin.js | 1 + lib/teams/server/team-updates-handler.js | 59 ++++++------------- lib/teams/server/team.js | 2 +- .../features/teams/templates/clan-accept.html | 4 ++ .../teams/templates/clan-container.html | 10 ++++ .../features/teams/templates/clan-create.html | 3 + .../features/teams/templates/clan-invite.html | 3 + .../teams/templates/clan-player-data.html | 9 +++ .../features/teams/templates/clan-remove.html | 3 + theme/plugins/objects/server/quest-npc.js | 9 ++- theme/plugins/server-plugin.js | 2 + 23 files changed, 250 insertions(+), 79 deletions(-) create mode 100644 lib/teams/server/clan-members-data-mapper.js create mode 100644 lib/teams/server/players-data-mapper.js create mode 100644 theme/default/assets/features/teams/templates/clan-accept.html create mode 100644 theme/default/assets/features/teams/templates/clan-container.html create mode 100644 theme/default/assets/features/teams/templates/clan-create.html create mode 100644 theme/default/assets/features/teams/templates/clan-invite.html create mode 100644 theme/default/assets/features/teams/templates/clan-player-data.html create mode 100644 theme/default/assets/features/teams/templates/clan-remove.html diff --git a/lib/teams/client/clan-initializer-handler.js b/lib/teams/client/clan-initializer-handler.js index 4b20c95a2..04e785a08 100644 --- a/lib/teams/client/clan-initializer-handler.js +++ b/lib/teams/client/clan-initializer-handler.js @@ -15,6 +15,8 @@ class ClanInitializerHandler if(0 === Object.keys(clanData).length){ return false; } + let currentPlayer = teamsPlugin.gameManager.getCurrentPlayer(); + currentPlayer.clan = clanData; teamsPlugin.events.on('reldens.createUiScene', (preloadScene) => { // this.clanUi = new ClanUi(preloadScene); // this.clanUi.createUi(); diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 7ad6a08dd..527a53e9f 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -25,15 +25,16 @@ class TeamsPlugin extends PluginInterface if (!this.events) { Logger.error('EventsManager undefined in TeamsPlugin.'); } + this.events.on('reldens.beforeCreateEngine', (initialGameData) => { + ClanInitializerHandler.appendCreateUiListener(initialGameData, this); + }); this.events.on('reldens.preloadUiScene', (preloadScene) => { TemplatesHandler.preloadTemplates(preloadScene); }); this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { + TeamTargetBoxEnricher.appendClanInviteButton(this.gameManager, target, previousTarget, targetName); TeamTargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); }); - this.events.on('reldens.beforeCreateEngine', (initialGameData) => { - ClanInitializerHandler.appendCreateUiListener(initialGameData, this); - }); this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 66f19e3bb..34523a947 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -64,7 +64,7 @@ class TeamMessageHandler id: this.teamUiKey(), title: this.gameManager.config.getWithoutLogs( 'client/teams/labels/requestFromTitle', - TeamsConst.LABELS.TEAM_REQUEST_FROM + TeamsConst.LABELS.TEAM.REQUEST_FROM ), content: this.message.from, options: this.gameManager.config.get('client/ui/options/acceptOrDecline'), @@ -99,7 +99,7 @@ class TeamMessageHandler this.createTeamUi(teamUiKey); let title = this.gameManager.config.getWithoutLogs( 'client/team/labels/leaderNameTitle', - TeamsConst.LABELS.LEADER_NAME_TITLE + TeamsConst.LABELS.TEAM.LEADER_NAME_TITLE ).replace('%leaderName', this.message.leaderName); let container = this.gameManager.gameDom.getElement('#box-'+teamUiKey+' .box-content'); if(!container){ @@ -159,8 +159,8 @@ class TeamMessageHandler let playerId = this.gameManager.getCurrentPlayer().playerId; let isPlayerOwner = playerId === this.message.id; let leaveActionLabel = isPlayerOwner - ? this.gameManager.config.getWithoutLogs('client/team/titles/disbandLabel', TeamsConst.LABELS.DISBAND) - : this.gameManager.config.getWithoutLogs('client/team/titles/leaveLabel', TeamsConst.LABELS.LEAVE); + ? this.gameManager.config.getWithoutLogs('client/team/titles/disbandLabel', TeamsConst.LABELS.TEAM.DISBAND) + : this.gameManager.config.getWithoutLogs('client/team/titles/leaveLabel', TeamsConst.LABELS.TEAM.LEAVE); let templateParams = { teamId: this.message.id, playerId, @@ -224,7 +224,7 @@ class TeamMessageHandler if('' !== propertyMaxValue){ propertyMaxValue = this.gameManager.config.getWithoutLogs( 'client/team/labels/propertyMaxValue', - TeamsConst.LABELS.PROPERTY_MAX_VALUE + TeamsConst.LABELS.TEAM.PROPERTY_MAX_VALUE ).replace('%propertyMaxValue', propertyMaxValue); } sharedPropertiesContent += this.gameManager.gameEngine.parseTemplate(templateContent, { diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/team-target-box-enricher.js index be272130d..7110588b1 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -11,24 +11,49 @@ const { Logger, sc } = require('@reldens/utils'); class TeamTargetBoxEnricher { + static appendClanInviteButton(gameManager, target, previousTarget, targetName) + { + let currentPlayer = gameManager.getCurrentPlayer(); + if(!currentPlayer.clan){ + return false; + } + if(!this.targetIsValidPlayer(target, currentPlayer)){ + return false; + } + if(currentPlayer.clan.members[target.id]){ + return false; + } + + /* + if(gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id)){ + return false; + } + */ + return this.appendInviteButton('clan', target, gameManager, targetName); + } + static appendTeamInviteButton(gameManager, target, previousTarget, targetName) { - if( - GameConst.TYPE_PLAYER !== target.type - || gameManager.getCurrentPlayer().playerId === target.id - || gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id) - ){ + if(!this.targetIsValidPlayer(target, gameManager)){ return false; } + if(gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id)){ + return false; + } + return this.appendInviteButton('team', target, gameManager, targetName); + } + + static appendInviteButton(type, target, gameManager, targetName) + { let uiScene = gameManager.gameEngine.uiScene; let uiTarget = sc.get(uiScene, 'uiTarget', false); if(false === uiTarget){ Logger.critical('Missing "uiTarget" on uiScene.'); return false; } - let teamPlayerActionsTemplate = uiScene.cache.html.get('teamPlayerInvite'); + let teamPlayerActionsTemplate = uiScene.cache.html.get(type+'PlayerInvite'); if(!teamPlayerActionsTemplate){ - Logger.critical('Template "teamPlayerInvite" not found.'); + Logger.critical('Template "'+type+'PlayerInvite" not found.'); return false; } gameManager.gameDom.appendToElement( @@ -39,19 +64,26 @@ class TeamTargetBoxEnricher // @TODO - BETA - Create translations table with a loader and processor. playerName: targetName, playerId: target.id, - inviteLabel: gameManager.config.get('team/titles/inviteLabel', TeamsConst.LABELS.INVITE_BUTTON_LABEL) + inviteLabel: gameManager.config.get( + type+'/titles/inviteLabel', + TeamsConst.LABELS[type.toUpperCase()].INVITE_BUTTON_LABEL + ) } ) ); - let inviteButton = gameManager.gameDom.getElement('.team-invite-'+target.id+' button'); + let inviteButton = gameManager.gameDom.getElement('.'+type+'-invite-' + target.id + ' button'); inviteButton?.addEventListener('click', () => { - let sendData = {act: TeamsConst.ACTIONS.TEAM_INVITE, id: target.id}; + let sendData = {act: TeamsConst.ACTIONS[type.toUpperCase()+'_INVITE'], id: target.id}; gameManager.room.send('*', sendData); inviteButton.style.display = 'none'; gameManager.gameEngine.clearTarget(); }); } + static targetIsValidPlayer(target, currentPlayer) + { + return GameConst.TYPE_PLAYER === target.type && currentPlayer.playerId !== target.id; + } } module.exports.TeamTargetBoxEnricher = TeamTargetBoxEnricher; diff --git a/lib/teams/client/templates-handler.js b/lib/teams/client/templates-handler.js index 886f2c052..8bf88d1e7 100644 --- a/lib/teams/client/templates-handler.js +++ b/lib/teams/client/templates-handler.js @@ -19,6 +19,12 @@ class TemplatesHandler preloadScene.load.html('teamRemove', teamsTemplatePath+'team-remove.html'); preloadScene.load.html('teamContainer', teamsTemplatePath+'team-container.html'); preloadScene.load.html('teamPlayerData', teamsTemplatePath+'team-player-data.html'); + preloadScene.load.html('clanCreate', teamsTemplatePath+'clan-create.html'); + preloadScene.load.html('clanPlayerInvite', teamsTemplatePath+'clan-invite.html'); + preloadScene.load.html('clanPlayerAccept', teamsTemplatePath+'clan-accept.html'); + preloadScene.load.html('clanRemove', teamsTemplatePath+'clan-remove.html'); + preloadScene.load.html('clanContainer', teamsTemplatePath+'clan-container.html'); + preloadScene.load.html('clanPlayerData', teamsTemplatePath+'clan-player-data.html'); preloadScene.load.html('teamsSharedProperty', teamsTemplatePath+'shared-property.html'); } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 4603b89cc..d541f1366 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -24,13 +24,25 @@ module.exports.TeamsConst = { CLAN_ACCEPTED: clanPref+'acp', CLAN_LEAVE: clanPref+'lev', CLAN_UPDATE: clanPref+'upd', + CLAN_LEFT: clanPref+'lef', + CLAN_REMOVE: clanPref+'rem', }, LABELS: { - INVITE_BUTTON_LABEL: 'Team - Invite', - TEAM_REQUEST_FROM: 'Accept team request from:', - LEADER_NAME_TITLE: 'Team leader: %leaderName', - DISBAND: 'Disband Team', - LEAVE: 'Leave Team', - PROPERTY_MAX_VALUE: '/ %propertyMaxValue' + TEAM: { + INVITE_BUTTON_LABEL: 'Team - Invite', + REQUEST_FROM: 'Accept team request from:', + LEADER_NAME_TITLE: 'Team leader: %leaderName', + DISBAND: 'Disband Team', + LEAVE: 'Leave Team', + PROPERTY_MAX_VALUE: '/ %propertyMaxValue' + }, + CLAN: { + INVITE_BUTTON_LABEL: 'Clan - Invite', + REQUEST_FROM: 'Accept clan request from:', + LEADER_NAME_TITLE: 'Clan leader: %leaderName', + DISBAND: 'Disband Clan', + LEAVE: 'Leave Clan', + PROPERTY_MAX_VALUE: '/ %propertyMaxValue' + } } }; diff --git a/lib/teams/server/clan-members-data-mapper.js b/lib/teams/server/clan-members-data-mapper.js new file mode 100644 index 000000000..004a5d625 --- /dev/null +++ b/lib/teams/server/clan-members-data-mapper.js @@ -0,0 +1,27 @@ +/** + * + * Reldens - ClanMembersDataMapper + * + */ + +class ClanMembersDataMapper +{ + + static fetchPlayersData(clan) + { + let teamPlayersId = Object.keys(clan.members); + let membersData = {}; + for(let i of teamPlayersId){ + membersData[i] = { + name: clan.members[i].playerName, + level: clan.members[i].level, + id: clan.members[i].player_id, + sessionId: clan.members[i].sessionId, + }; + } + return membersData; + } + +} + +module.exports.ClanMembersDataMapper = ClanMembersDataMapper; diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index 4ea764bd7..c86ef35c2 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -14,13 +14,17 @@ class Clan extends Team super(props); this.name = sc.get(props, 'name', ''); this.points = sc.get(props, 'points', ''); + this.members = sc.get(props, 'members', ''); } - static fromModel(clanModel, owner, sharedProperties) + static fromModel(clanModel, owner, ownerClient, members, sharedProperties) { return new this({ owner, + ownerClient, + members, sharedProperties, + id: clanModel.id, ownerId: clanModel.owner_id, name: clanModel.name, points: clanModel.points, @@ -33,7 +37,8 @@ class Clan extends Team return { name: this.name, points: this.points, - level: this.level + level: this.level, + ownerId: this.owner.id, }; } diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index abce458b8..981525a9d 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -6,6 +6,7 @@ const { Clan } = require('../clan'); const { TeamsConst } = require('../../constants'); +const { sc } = require('@reldens/utils'); class CreatePlayerClanHandler { @@ -20,16 +21,13 @@ class CreatePlayerClanHandler let clanModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneByWithRelations( 'player_id', playerSchema.player_id, - ['parent_clan.parent_level.modifiers'] + ['parent_clan.[members.parent_player, parent_level.modifiers]'] ); if(!clanModel){ return false; } - let clan = Clan.fromModel( - clanModel.parent_clan, - playerSchema, - room.config.get('client/ui/teams/sharedProperties') - ); + let clan = this.loadClan(teamsPlugin, clanModel, playerSchema, client, room); + clan.join(playerSchema, client); playerSchema.privateData.clan = clan; let sendData = { act: TeamsConst.ACTIONS.CLAN_UPDATE, @@ -43,6 +41,20 @@ class CreatePlayerClanHandler client.send('*', sendData); } + static loadClan(teamsPlugin, clanModel, playerSchema, client, room) + { + let clan = sc.get(teamsPlugin.clans, clanModel.id, false); + if(false === clan){ + clan = Clan.fromModel( + clanModel.parent_clan, + playerSchema, + client, + room.config.get('client/ui/teams/sharedProperties') + ); + teamsPlugin.clans[clan.id] = clan; + } + return clan; + } } module.exports.CreatePlayerClanHandler = CreatePlayerClanHandler; diff --git a/lib/teams/server/models/objection-js/clan-members-model.js b/lib/teams/server/models/objection-js/clan-members-model.js index e1055d249..32733d4b5 100644 --- a/lib/teams/server/models/objection-js/clan-members-model.js +++ b/lib/teams/server/models/objection-js/clan-members-model.js @@ -28,11 +28,11 @@ class ClanMembersModel extends ObjectionJsRawModel } }, parent_player: { - relation: this.HasManyRelation, + relation: this.HasOneRelation, modelClass: PlayersModel, join: { - from: this.tableName+'.id', - to: PlayersModel.tableName+'.player_id' + from: this.tableName+'.player_id', + to: PlayersModel.tableName+'.id' } } } diff --git a/lib/teams/server/models/objection-js/clan-model.js b/lib/teams/server/models/objection-js/clan-model.js index 6dd5119d2..f8768ae5d 100644 --- a/lib/teams/server/models/objection-js/clan-model.js +++ b/lib/teams/server/models/objection-js/clan-model.js @@ -5,7 +5,7 @@ */ const { ObjectionJsRawModel } = require('@reldens/storage'); -const {PlayersModel} = require("../../../../users/server/models/objection-js/players-model"); +const {ClanMembersModel} = require("./clan-members-model"); class ClanModel extends ObjectionJsRawModel { @@ -19,6 +19,7 @@ class ClanModel extends ObjectionJsRawModel { let { PlayersModel } = require('../../../../users/server/models/objection-js/players-model'); let { ClanLevelsModel } = require('./clan-levels-model'); + let { ClanMembersModel } = require('./clan-members-model'); return { player_owner: { relation: this.HasOneRelation, @@ -35,6 +36,14 @@ class ClanModel extends ObjectionJsRawModel from: this.tableName+'.level', to: ClanLevelsModel.tableName+'.key' } + }, + members: { + relation: this.HasManyRelation, + modelClass: ClanMembersModel, + join: { + from: this.tableName+'.id', + to: ClanMembersModel.tableName+'.clan_id' + } } } } diff --git a/lib/teams/server/players-data-mapper.js b/lib/teams/server/players-data-mapper.js new file mode 100644 index 000000000..c4e0fa6b3 --- /dev/null +++ b/lib/teams/server/players-data-mapper.js @@ -0,0 +1,46 @@ +/** + * + * Reldens - PlayersDataMapper + * + */ + +const { sc } = require('@reldens/utils'); + +class PlayersDataMapper +{ + + static fetchPlayersData(team) + { + let teamPlayersId = Object.keys(team.players); + let playersData = {}; + for(let i of teamPlayersId){ + playersData[i] = this.fetchPlayerData(team.players[i], team.sharedProperties); + } + return playersData; + } + + static fetchPlayerData(playerSchema, sharedProperties) + { + let playerData = { + name: playerSchema.playerName, + id: playerSchema.player_id, + sessionId: playerSchema.sessionId, + sharedProperties: {} + }; + for(let i of Object.keys(sharedProperties)){ + let propertyData = sharedProperties[i]; + playerData.sharedProperties[i] = { + label: propertyData.label, + value: sc.getByPath(playerSchema, propertyData.path.split('/'), 0), + }; + let pathMax = sc.get(propertyData, 'pathMax', ''); + if('' !== pathMax){ + playerData.sharedProperties[i].max = sc.getByPath(playerSchema, pathMax.split('/'), 0); + } + } + return playerData; + } + +} + +module.exports.PlayersDataMapper = PlayersDataMapper; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 5932907b9..04d652f1b 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -26,6 +26,7 @@ class TeamsPlugin extends PluginInterface Logger.error('DataServer undefined in TeamsPlugin.'); } this.teams = sc.get(props, 'teams', {}); + this.clans = sc.get(props, 'clans', {}); this.changingRoomPlayers = sc.get(props, 'changingRoomPlayers', {}); this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { roomMessageActions.teams = new TeamsMessageActions({teamsPlugin: this}); diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 144242b76..87a91a27d 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -1,32 +1,42 @@ /** * - * Reldens - TeamsMessageActions + * Reldens - TeamUpdatesHandler * */ +const { PlayersDataMapper } = require('./players-data-mapper'); const { TeamsConst } = require('../constants'); -const { sc } = require('@reldens/utils'); class TeamUpdatesHandler { static updateTeamPlayers(team) + { + return this.updatePlayers(team, TeamsConst.ACTIONS.TEAM_UPDATE, TeamsConst.KEY); + } + + static updateClanPlayers(clan) + { + return this.updatePlayers(clan, TeamsConst.ACTIONS.CLAN_UPDATE, TeamsConst.CLAN_KEY); + } + + static updatePlayers(team, act, listener) { let clientsKeys = Object.keys(team.clients); - if(0 === clientsKeys.length){ + if (0 === clientsKeys.length) { return false; } - let playersList = this.fetchPlayersData(team); - if(0 === Object.keys(playersList).length){ + let playersList = PlayersDataMapper.fetchPlayersData(team); + if (0 === Object.keys(playersList).length) { return false; } - for(let i of clientsKeys){ + for (let i of clientsKeys) { let otherPlayersData = Object.assign({}, playersList); delete otherPlayersData[i]; let sendUpdate = { - act: TeamsConst.ACTIONS.TEAM_UPDATE, + act, id: team.ownerClient.id, - listener: TeamsConst.KEY, + listener, players: otherPlayersData, leaderName: team.owner.playerName, }; @@ -34,39 +44,6 @@ class TeamUpdatesHandler } return true; } - - static fetchPlayersData(team) - { - let teamPlayersId = Object.keys(team.players); - let playersData = {}; - for(let i of teamPlayersId){ - playersData[i] = this.fetchPlayerData(team.players[i], team.sharedProperties); - } - return playersData; - } - - static fetchPlayerData(playerSchema, sharedProperties) - { - let playerData = { - name: playerSchema.playerName, - id: playerSchema.player_id, - sessionId: playerSchema.sessionId, - sharedProperties: {} - }; - for(let i of Object.keys(sharedProperties)){ - let propertyData = sharedProperties[i]; - playerData.sharedProperties[i] = { - label: propertyData.label, - value: sc.getByPath(playerSchema, propertyData.path.split('/'), 0), - }; - let pathMax = sc.get(propertyData, 'pathMax', ''); - if('' !== pathMax){ - playerData.sharedProperties[i].max = sc.getByPath(playerSchema, pathMax.split('/'), 0); - } - } - return playerData; - } - } module.exports.TeamUpdatesHandler = TeamUpdatesHandler; diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index 05f24b68c..51dd8a584 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -19,7 +19,7 @@ class Team ErrorManager.error('Team owner undefined.', props); } this.ownerClient = sc.get(props, 'ownerClient', false); - if(false === this.owner){ + if(false === this.ownerClient){ ErrorManager.error('Team owner client undefined.', props); } let players = {}; diff --git a/theme/default/assets/features/teams/templates/clan-accept.html b/theme/default/assets/features/teams/templates/clan-accept.html new file mode 100644 index 000000000..a9998fbb5 --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-accept.html @@ -0,0 +1,4 @@ +
+ {{acceptTeamFromPlayerLabel}} + +
diff --git a/theme/default/assets/features/teams/templates/clan-container.html b/theme/default/assets/features/teams/templates/clan-container.html new file mode 100644 index 000000000..40a2c4dfe --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-container.html @@ -0,0 +1,10 @@ +
+
+ {{&clanMembers}} +
+
+
+ +
+
+
diff --git a/theme/default/assets/features/teams/templates/clan-create.html b/theme/default/assets/features/teams/templates/clan-create.html new file mode 100644 index 000000000..d2677bb6a --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-create.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/theme/default/assets/features/teams/templates/clan-invite.html b/theme/default/assets/features/teams/templates/clan-invite.html new file mode 100644 index 000000000..8743e9fb2 --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-invite.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/theme/default/assets/features/teams/templates/clan-player-data.html b/theme/default/assets/features/teams/templates/clan-player-data.html new file mode 100644 index 000000000..76b31418e --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-player-data.html @@ -0,0 +1,9 @@ +
+
+ {{playerName}} +
+ {{&clanRemove}} +
+ {{&playerProperties}} +
+
diff --git a/theme/default/assets/features/teams/templates/clan-remove.html b/theme/default/assets/features/teams/templates/clan-remove.html new file mode 100644 index 000000000..62a0b53c9 --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-remove.html @@ -0,0 +1,3 @@ +
+ remove +
diff --git a/theme/plugins/objects/server/quest-npc.js b/theme/plugins/objects/server/quest-npc.js index 337d379a4..830fb893d 100644 --- a/theme/plugins/objects/server/quest-npc.js +++ b/theme/plugins/objects/server/quest-npc.js @@ -45,7 +45,13 @@ class QuestNpc extends NpcObject client.send('*', {act: GameConst.UI, id: this.id, content: contentMessage}); return false; } - await playerSchema.inventory.manager.decreaseItemQty(treeItem.uid, 1); + let removeResult = await playerSchema.inventory.manager.decreaseItemQty(treeItem.uid, 1); + if(false === removeResult){ + Logger.error(`Error while adding item "${selectedOption.key}"`); + let contentMessage = 'Sorry, I was not able to remove the required item, contact the administrator.'; + client.send('*', {act: GameConst.UI, id: this.id, content: contentMessage}); + return false; + } // add the new coin: coinsItem = playerSchema.inventory.manager.createItemInstance('coins'); if(false === await playerSchema.inventory.manager.addItem(coinsItem)){ @@ -56,6 +62,7 @@ class QuestNpc extends NpcObject } let activationData = {act: GameConst.UI, id: this.id, content: 'All yours!'}; client.send('*', activationData); + return true; } } diff --git a/theme/plugins/server-plugin.js b/theme/plugins/server-plugin.js index 776cd94bf..5ee0adab3 100644 --- a/theme/plugins/server-plugin.js +++ b/theme/plugins/server-plugin.js @@ -35,6 +35,8 @@ class ServerPlugin extends PluginInterface if(!sc.hasOwn(customClasses, 'roomsClass')){ customClasses.roomsClass = {}; } + // @TODO - BETA - Clean up all the custom classes, by default these can be all default objects with all the + // data coming from the storage. Leave just a custom class as sample like the "Npc1" on the client-plugin. customClasses.objects['door_1'] = Door; customClasses.objects['door_2'] = Door; customClasses.objects['npc_1'] = NpcObject; From 8605d93b3c5bc5fcca4307f0705fd1506d0c0bc0 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 15 Mar 2023 21:42:30 +0100 Subject: [PATCH 27/82] - Reldens - v4.0.0 - Logger fix on browser. - Clan server and client updates. --- .env.dist | 4 +- lib/game/client/game-engine.js | 2 +- lib/game/client/manager.js | 5 +- lib/game/client/scene-preloader.js | 4 +- lib/rooms/server/game.js | 8 +- lib/teams/client/clan-initializer-handler.js | 23 +++++- lib/teams/client/clan-message-handler.js | 34 ++++++++ lib/teams/client/clan-message-listener.js | 62 ++++++++++++++ lib/teams/client/plugin.js | 36 +++++++- lib/teams/client/team-message-handler.js | 24 +----- lib/teams/client/team-message-listener.js | 30 ++++++- lib/teams/client/team-target-box-enricher.js | 8 +- lib/teams/constants.js | 2 +- lib/teams/server/clan-updates-handler.js | 41 ++++++++++ lib/teams/server/clan.js | 40 ++++++++- .../create-player-clan-handler.js | 48 ++++++----- ...nd-player-hit-change-point-team-handler.js | 3 +- .../event-handlers/stats-update-handler.js | 47 +++++++++++ .../stats-update-team-handler.js | 30 ------- .../server/message-actions/try-team-start.js | 2 +- .../models/objection-js/clan-levels-model.js | 1 - .../server/models/objection-js/clan-model.js | 1 - lib/teams/server/plugin.js | 5 +- lib/teams/server/team-updates-handler.js | 17 +--- lib/users/client/lifebar-ui.js | 4 +- package-lock.json | 82 +++++++++++++++++-- package.json | 2 +- theme/default/index.js | 7 +- 28 files changed, 442 insertions(+), 130 deletions(-) create mode 100644 lib/teams/client/clan-message-handler.js create mode 100644 lib/teams/client/clan-message-listener.js create mode 100644 lib/teams/server/clan-updates-handler.js create mode 100644 lib/teams/server/event-handlers/stats-update-handler.js delete mode 100644 lib/teams/server/event-handlers/stats-update-team-handler.js diff --git a/.env.dist b/.env.dist index 0634ce834..bcea0e9c5 100644 --- a/.env.dist +++ b/.env.dist @@ -36,8 +36,8 @@ RELDENS_DB_LIMIT=0 # RELDENS_STORAGE_DRIVER=mikro-orm RELDENS_STORAGE_DRIVER=objection-js # Logs: -RELDENS_LOG_LEVEL=1 -# RELDENS_ENABLE_TRACE_FOR= +RELDENS_LOG_LEVEL=9 +RELDENS_ENABLE_TRACE_FOR="emergency,alert,critical,error,warning" # Mailer: RELDENS_MAILER_ENABLE=0 RELDENS_MAILER_SERVICE=gmail diff --git a/lib/game/client/game-engine.js b/lib/game/client/game-engine.js index fc8d8b8e5..6d48fd250 100644 --- a/lib/game/client/game-engine.js +++ b/lib/game/client/game-engine.js @@ -55,7 +55,7 @@ class GameEngine extends Game uiElement.y = uiY; } this.eventsManager.emit('reldens.updateGameSizeAfter', this, newWidth, newHeight); - }, manager.config.get('client/general/gameEngine/updateGameSizeTimeOut', 500)); + }, manager.config.getWithoutLogs('client/general/gameEngine/updateGameSizeTimeOut', 500)); } getCurrentScreenSize(manager) diff --git a/lib/game/client/manager.js b/lib/game/client/manager.js index 94dbd01a5..0c0f60863 100644 --- a/lib/game/client/manager.js +++ b/lib/game/client/manager.js @@ -14,7 +14,7 @@ const { GameDom } = require('./game-dom'); const { ConfigManager } = require('../../config/client/config-manager'); const { GameConst } = require('../constants'); const { FirebaseConnector } = require('../../firebase/client/connector'); -const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils'); +const { ErrorManager, EventsManagerSingleton, Logger, sc } = require('@reldens/utils'); class GameManager { @@ -132,7 +132,7 @@ class GameManager { await this.events.emit('reldens.beforeInitEngineAndStartGame', this.initialGameData, this); if(!sc.hasOwn(this.initialGameData, 'gameConfig')){ - throw new Error('ERROR - Missing game configuration.'); + ErrorManager.error('Missing game configuration.'); } // apply the initial config to the processor: sc.deepMergeProperties(this.config, (this.initialGameData?.gameConfig || {})); @@ -324,6 +324,7 @@ class GameManager displayForgotPassword() { + // @TODO - BETA - Extract. this.gameDom.getJSON(this.appServerUrl+'/reldens-mailer-enabled', (err, response) => { if(!response.enabled){ return; diff --git a/lib/game/client/scene-preloader.js b/lib/game/client/scene-preloader.js index 73cb00a18..8a7300347 100644 --- a/lib/game/client/scene-preloader.js +++ b/lib/game/client/scene-preloader.js @@ -265,7 +265,7 @@ class ScenePreloader extends Scene getUiConfig(uiName, newWidth, newHeight) { let {uiX, uiY} = this.getUiPosition(uiName, newWidth, newHeight); - return {enabled: this.gameManager.config.get('client/ui/'+uiName+'/enabled'), uiX, uiY} + return {enabled: this.gameManager.config.getWithoutLogs('client/ui/'+uiName+'/enabled'), uiX, uiY} } getUiPosition(uiName, newWidth, newHeight) @@ -273,7 +273,7 @@ class ScenePreloader extends Scene if('' === uiName){ uiName = 'default'; } - let uiConfig = this.gameManager.config.get('client/ui/'+uiName); + let uiConfig = this.gameManager.config.getWithoutLogs('client/ui/'+uiName, {}); let uiX = sc.get(uiConfig, 'x', 0); let uiY = sc.get(uiConfig, 'y', 0); if(this.gameManager.config.get('client/ui/screen/responsive')){ diff --git a/lib/rooms/server/game.js b/lib/rooms/server/game.js index 5d3d82cd3..917bed7d8 100644 --- a/lib/rooms/server/game.js +++ b/lib/rooms/server/game.js @@ -27,7 +27,6 @@ class RoomGame extends RoomLogin async onJoin(client, options, authResult) { await this.events.emit('reldens.onJoinRoomGame', client, options, authResult, this); - // update last login: await this.loginManager.updateLastLogin(authResult); // we need to send the engine and all the general and client configurations from the storage: let storedClientConfig = {client: this.config.client}; @@ -36,13 +35,12 @@ class RoomGame extends RoomLogin act: GameConst.START_GAME, sessionId: client.sessionId, players: authResult.players, - // if multiplayer is disabled then we will use the first one as default: - player: authResult.players ? authResult.players[0] : false, + // @NOTE: if multiplayer is disabled then we will use the first one as default: + player: 0 < sc.length(authResult.players) ? authResult.players[0] : false, gameConfig: clientFullConfig, features: this.config.availableFeaturesList }; - await this.events.emit('reldens.beforeSuperInitialGameData', superInitialGameData, this); - // client start: + await this.events.emit('reldens.beforeSuperInitialGameData', superInitialGameData, this, client); client.send('*', superInitialGameData); } diff --git a/lib/teams/client/clan-initializer-handler.js b/lib/teams/client/clan-initializer-handler.js index 04e785a08..6d9490deb 100644 --- a/lib/teams/client/clan-initializer-handler.js +++ b/lib/teams/client/clan-initializer-handler.js @@ -4,6 +4,8 @@ * */ +const { UserInterface } = require('../../game/client/user-interface'); +const { TeamsConst } = require('../constants'); const { sc } = require('@reldens/utils'); class ClanInitializerHandler @@ -18,10 +20,27 @@ class ClanInitializerHandler let currentPlayer = teamsPlugin.gameManager.getCurrentPlayer(); currentPlayer.clan = clanData; teamsPlugin.events.on('reldens.createUiScene', (preloadScene) => { - // this.clanUi = new ClanUi(preloadScene); - // this.clanUi.createUi(); + this.createClanUi(clanData, teamsPlugin.gameManager, preloadScene); }); + } + static createClanUi(clanData, gameManager, preloadScene) + { + let clanUi = sc.get(preloadScene.userInterfaces, clanData.ownerId); + if(clanUi){ + return clanUi; + } + if(!preloadScene.userInterfaces){ + preloadScene.userInterfaces = {}; + } + preloadScene.userInterfaces[clanData.ownerId] = new UserInterface( + gameManager, + {id: clanData.ownerId, type: TeamsConst.KEY, defaultOpen: true, defaultClose: true}, + 'assets/features/teams/templates/ui-clan.html', + TeamsConst.KEY + ); + preloadScene.userInterfaces[clanData.ownerId].createUiElement(preloadScene, TeamsConst.KEY); + return preloadScene.userInterfaces[clanData.ownerId]; } } diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js new file mode 100644 index 000000000..54953a92b --- /dev/null +++ b/lib/teams/client/clan-message-handler.js @@ -0,0 +1,34 @@ +/** + * + * Reldens - ClanMessageHandler + * + */ + +const { TeamMessageHandler } = require('./team-message-handler'); + +class ClanMessageHandler extends TeamMessageHandler +{ + + constructor(props) + { + super(props); + } + + showClanRequest() + { + + } + + showClanBox() + { + + } + + removeClanUi() + { + + } + +} + +module.exports.ClanMessageHandler = ClanMessageHandler; diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js new file mode 100644 index 000000000..97598e22b --- /dev/null +++ b/lib/teams/client/clan-message-listener.js @@ -0,0 +1,62 @@ +/** + * + * Reldens - ClanMessageListener + * + */ + +const { ClanMessageHandler } = require('./clan-message-handler'); +const { TeamsConst } = require('../constants'); +const { Logger, sc } = require('@reldens/utils'); + +class ClanMessageListener +{ + + async executeClientMessageActions(props) + { + let message = sc.get(props, 'message', false); + if(!message){ + Logger.error('Missing message data on ClanMessageListener.', props); + return false; + } + let roomEvents = sc.get(props, 'roomEvents', false); + if(!roomEvents){ + Logger.error('Missing RoomEvents on ClanMessageListener.', props); + return false; + } + let clanMessageHandler = new ClanMessageHandler({roomEvents, message}); + if(!clanMessageHandler.validate()){ + if(this.isClanMessage(message)){ + if(!roomEvents.clanMessagesQueue){ + roomEvents.clanMessagesQueue = []; + } + roomEvents.clanMessagesQueue.push(message); + } + return false; + } + if(!this.isClanMessage(message)){ + return false; + } + return this.handleClanMessage(message, clanMessageHandler); + } + + handleClanMessage(message, clanMessageHandler) + { + if(TeamsConst.ACTIONS.CLAN_INVITE === message.act){ + return clanMessageHandler.showClanRequest(); + } + if(TeamsConst.ACTIONS.CLAN_UPDATE === message.act){ + return clanMessageHandler.showClanBox(); + } + if(TeamsConst.ACTIONS.CLAN_LEFT === message.act){ + return clanMessageHandler.removeClanUi(); + } + return true; + } + + isClanMessage(message) + { + return 0 === message.act.indexOf(TeamsConst.CLAN_PREF); + } +} + +module.exports.ClanMessageListener = ClanMessageListener; diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 527a53e9f..9b56dbe84 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -7,11 +7,13 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); +const { ClanMessageListener } = require('./clan-message-listener'); const { TemplatesHandler } = require('./templates-handler'); const { ClanInitializerHandler } = require('./clan-initializer-handler'); const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); + class TeamsPlugin extends PluginInterface { @@ -25,8 +27,31 @@ class TeamsPlugin extends PluginInterface if (!this.events) { Logger.error('EventsManager undefined in TeamsPlugin.'); } + let teamMessageListener = new TeamMessageListener(); + let clanMessageListener = new ClanMessageListener(); this.events.on('reldens.beforeCreateEngine', (initialGameData) => { - ClanInitializerHandler.appendCreateUiListener(initialGameData, this); + // ClanInitializerHandler.appendCreateUiListener(initialGameData, this); + }); + this.events.on('reldens.createUiScene', (uiScene) => { + // this.uiManager = new SkillsUi(preloadScene); + // this.uiManager.createUi(); + let roomEvents = uiScene?.gameManager?.activeRoomEvents; + if(!roomEvents){ + Logger.critical('RoomEvents undefined for process Team messages queue on TeamsPlugin.'); + return false; + } + if(!sc.isArray(roomEvents.teamMessagesQueue) || 0 === roomEvents.teamMessagesQueue.length){ + return; + } + for(let message of roomEvents.teamMessagesQueue){ + teamMessageListener.handleTeamMessage({roomEvents, message}); + } + if(!sc.isArray(roomEvents.clanMessagesQueue) || 0 === roomEvents.clanMessagesQueue.length){ + return; + } + for(let message of roomEvents.clanMessagesQueue){ + clanMessageListener.handleClanMessage({roomEvents, message}); + } }); this.events.on('reldens.preloadUiScene', (preloadScene) => { TemplatesHandler.preloadTemplates(preloadScene); @@ -35,7 +60,14 @@ class TeamsPlugin extends PluginInterface TeamTargetBoxEnricher.appendClanInviteButton(this.gameManager, target, previousTarget, targetName); TeamTargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); }); - this.gameManager.config.client.message.listeners[TeamsConst.KEY] = new TeamMessageListener(); + // @TODO - BETA - Standardize, listeners on config or added by events like: + // this.events.on('reldens.activateRoom', (room) => { + // room.onMessage('*', (message) => { + // this.messagesHandler.processOrQueueMessage(message); + // }); + // }); + this.gameManager.config.client.message.listeners[TeamsConst.KEY] = teamMessageListener; + this.gameManager.config.client.message.listeners[TeamsConst.CLAN_KEY] = clanMessageListener; } fetchTeamPlayerBySessionId(sessionId) diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 34523a947..b33f30129 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -23,40 +23,24 @@ class TeamMessageHandler validate() { if(!this.roomEvents){ - Logger.error('Missing RoomEvents.'); + Logger.notice('Missing RoomEvents on TeamMessageHandler.'); return false; } if(!this.message){ - Logger.error('Missing message.'); + Logger.notice('Missing message on TeamMessageHandler.'); return false; } if(!this.gameManager){ - Logger.error('Missing GameManager.'); + Logger.notice('Missing GameManager on TeamMessageHandler.'); return false; } if(!this.uiScene){ - Logger.error('Missing UI Scene.'); + Logger.notice('Missing UI Scene on TeamMessageHandler.'); return false; } return true; } - updateContents() - { - if(0 !== this.message.act.indexOf(TeamsConst.TEAM_PREF)){ - return false; - } - if(TeamsConst.ACTIONS.TEAM_INVITE === this.message.act){ - return this.showTeamRequest(); - } - if(TeamsConst.ACTIONS.TEAM_UPDATE === this.message.act){ - return this.showTeamBox(); - } - if(TeamsConst.ACTIONS.TEAM_LEFT === this.message.act){ - this.removeTeamUi(); - } - } - showTeamRequest() { this.createTeamUi(this.teamUiKey()); diff --git a/lib/teams/client/team-message-listener.js b/lib/teams/client/team-message-listener.js index 981dd2152..efdbf540e 100644 --- a/lib/teams/client/team-message-listener.js +++ b/lib/teams/client/team-message-listener.js @@ -5,6 +5,7 @@ */ const { TeamMessageHandler } = require('./team-message-handler'); +const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); class TeamMessageListener @@ -24,12 +25,39 @@ class TeamMessageListener } let teamMessageHandler = new TeamMessageHandler({roomEvents, message}); if(!teamMessageHandler.validate()){ + if(this.isTeamMessage(message)){ + if(!roomEvents.teamMessagesQueue){ + roomEvents.teamMessagesQueue = []; + } + roomEvents.teamMessagesQueue.push(message); + } Logger.error('Invalid TeamMessageHandler', teamMessageHandler); return false; } - teamMessageHandler.updateContents(); + if(!this.isTeamMessage(message)){ + return false; + } + return this.handleTeamMessage(message, teamMessageHandler); + } + + handleTeamMessage(message, teamMessageHandler) + { + if(TeamsConst.ACTIONS.TEAM_INVITE === message.act){ + return teamMessageHandler.showTeamRequest(); + } + if(TeamsConst.ACTIONS.TEAM_UPDATE === message.act){ + return teamMessageHandler.showTeamBox(); + } + if(TeamsConst.ACTIONS.TEAM_LEFT === message.act){ + return teamMessageHandler.removeTeamUi(); + } + return true; } + isTeamMessage(message) + { + return 0 === message.act.indexOf(TeamsConst.TEAM_PREF); + } } module.exports.TeamMessageListener = TeamMessageListener; diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/team-target-box-enricher.js index 7110588b1..0039b39fd 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -23,18 +23,12 @@ class TeamTargetBoxEnricher if(currentPlayer.clan.members[target.id]){ return false; } - - /* - if(gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id)){ - return false; - } - */ return this.appendInviteButton('clan', target, gameManager, targetName); } static appendTeamInviteButton(gameManager, target, previousTarget, targetName) { - if(!this.targetIsValidPlayer(target, gameManager)){ + if(!this.targetIsValidPlayer(target, gameManager.getCurrentPlayer())){ return false; } if(gameManager.getFeature('teams').fetchTeamPlayerBySessionId(target.id)){ diff --git a/lib/teams/constants.js b/lib/teams/constants.js index d541f1366..a2e4c397a 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -5,7 +5,7 @@ */ let pref = 'tm.' -let clanPref = 'cl.'; +let clanPref = 'cln.'; module.exports.TeamsConst = { KEY: 'teams', diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js new file mode 100644 index 000000000..62cf3e51e --- /dev/null +++ b/lib/teams/server/clan-updates-handler.js @@ -0,0 +1,41 @@ +/** + * + * Reldens - ClanUpdatesHandler + * + */ + +const { PlayersDataMapper } = require('./players-data-mapper'); +const { TeamsConst } = require('../constants'); + +class ClanUpdatesHandler +{ + + static updateClanPlayers(clan) + { + let clientsKeys = Object.keys(clan.clients); + if (0 === clientsKeys.length) { + return false; + } + let playersList = PlayersDataMapper.fetchPlayersData(clan); + if(0 === Object.keys(playersList).length){ + return false; + } + for(let i of clientsKeys){ + let otherPlayersData = Object.assign({}, playersList); + delete otherPlayersData[i]; + let sendUpdate = { + act: TeamsConst.ACTIONS.CLAN_UPDATE, + id: clan.ownerClient.id, + listener: TeamsConst.CLAN_KEY, + players: otherPlayersData, + leaderName: clan.owner.playerName, + clan: clan.toArray() + }; + clan.clients[i].send('*', sendUpdate); + } + return true; + } + +} + +module.exports.ClanUpdatesHandler = ClanUpdatesHandler; diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index c86ef35c2..e9b1d23dd 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -12,18 +12,20 @@ class Clan extends Team constructor(props) { super(props); + this.id = sc.get(props, 'id', ''); this.name = sc.get(props, 'name', ''); this.points = sc.get(props, 'points', ''); - this.members = sc.get(props, 'members', ''); + this.members = sc.get(props, 'members', {}); } - static fromModel(clanModel, owner, ownerClient, members, sharedProperties) + static fromModel(props) { + let {clanModel, owner, ownerClient, sharedProperties} = props; return new this({ owner, ownerClient, - members, sharedProperties, + members: this.mapMembersFromModelArray(clanModel.members), id: clanModel.id, ownerId: clanModel.owner_id, name: clanModel.name, @@ -32,16 +34,46 @@ class Clan extends Team }); } - forSendData() + static mapMembersFromModelArray(membersCollection) + { + if(!sc.isArray(membersCollection) || 0 === membersCollection.length){ + return {}; + } + let mappedMembers = {}; + for(let memberModel of membersCollection){ + mappedMembers[memberModel.player_id] = memberModel; + } + return mappedMembers; + } + + toArray() { return { name: this.name, points: this.points, level: this.level, ownerId: this.owner.id, + members: this.mapMembersToArray() }; } + mapMembersToArray() + { + if(0 === sc.length(this.members)){ + return []; + } + let members = {}; + for(let i of Object.keys(this.members)){ + let member = this.members[i]; + members[i] = { + // @TODO - BETA - Make other members data optional, like level or class path. + name: member.parent_player.name, + id: i + } + } + return members; + } + } module.exports.Clan = Clan; \ No newline at end of file diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 981525a9d..4860623ad 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -5,8 +5,8 @@ */ const { Clan } = require('../clan'); -const { TeamsConst } = require('../../constants'); -const { sc } = require('@reldens/utils'); +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { Logger, sc } = require('@reldens/utils'); class CreatePlayerClanHandler { @@ -18,39 +18,45 @@ class CreatePlayerClanHandler if(!startEvent.continueProcess){ return false; } - let clanModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneByWithRelations( + let clanMemberModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneBy( 'player_id', - playerSchema.player_id, - ['parent_clan.[members.parent_player, parent_level.modifiers]'] + playerSchema.player_id ); - if(!clanModel){ + if(!clanMemberModel){ + return false; + } + let clan = await this.loadClan(teamsPlugin, clanMemberModel.clan_id, playerSchema, client, room); + if(!clan){ return false; } - let clan = this.loadClan(teamsPlugin, clanModel, playerSchema, client, room); clan.join(playerSchema, client); playerSchema.privateData.clan = clan; - let sendData = { - act: TeamsConst.ACTIONS.CLAN_UPDATE, - clan: clan.forSendData(), - }; - let endEvent = {client, playerSchema, room, teamsPlugin, sendData, continueProcess: true}; - teamsPlugin.events.emit('reldens.beforePlayerClanDataSend', endEvent); + let endEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; + teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClanUpdate', endEvent); if(!endEvent.continueProcess){ return false; } - client.send('*', sendData); + ClanUpdatesHandler.updateClanPlayers(clan); } - static loadClan(teamsPlugin, clanModel, playerSchema, client, room) + static async loadClan(teamsPlugin, clanId, playerSchema, client, room) { - let clan = sc.get(teamsPlugin.clans, clanModel.id, false); + let clan = sc.get(teamsPlugin.clans, clanId, false); if(false === clan){ - clan = Clan.fromModel( - clanModel.parent_clan, - playerSchema, - client, - room.config.get('client/ui/teams/sharedProperties') + let clanModel = await teamsPlugin.dataServer.getEntity('clan').loadByIdWithRelations( + clanId, + ['members.parent_player, parent_level.modifiers'] ); + if(!clanModel){ + Logger.error('Clan not found by ID "'+clanId+'".'); + return false; + } + clan = Clan.fromModel({ + clanModel, + owner: playerSchema, + ownerClient: client, + sharedProperties: room.config.get('client/ui/teams/sharedProperties') + }); teamsPlugin.clans[clan.id] = clan; } return clan; diff --git a/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js index 62efa3a65..6bacbb3e8 100644 --- a/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js +++ b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js @@ -3,7 +3,8 @@ * Reldens - EndPlayerHitChangePointTeamHandler * */ -const {Logger} = require("@reldens/utils"); + +const { Logger } = require('@reldens/utils'); class EndPlayerHitChangePointTeamHandler { diff --git a/lib/teams/server/event-handlers/stats-update-handler.js b/lib/teams/server/event-handlers/stats-update-handler.js new file mode 100644 index 000000000..ff27a00b4 --- /dev/null +++ b/lib/teams/server/event-handlers/stats-update-handler.js @@ -0,0 +1,47 @@ +/** + * + * Reldens - StatsUpdateHandler + * + */ + +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { TeamUpdatesHandler } = require('../team-updates-handler'); +const { Logger, sc } = require('@reldens/utils'); + + +class StatsUpdateHandler +{ + + static async updateTeam(props) + { + let {teamsPlugin, playerSchema} = props; + let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); + if('' === currentTeamId){ + return; + } + let currentTeam = sc.get(teamsPlugin.teams, currentTeamId, false); + if(!currentTeam){ + Logger.error('Team not found: '+ currentTeamId); + return; + } + return TeamUpdatesHandler.updateTeamPlayers(currentTeam); + } + + static async updateClan(props) + { + let {teamsPlugin, playerSchema} = props; + let clanId = sc.get(playerSchema.privateData.clan, 'id', ''); + if('' === clanId){ + return; + } + let currentClan = sc.get(teamsPlugin.clans, clanId, false); + if(!currentClan){ + Logger.error('Clan not found: '+ clanId); + return; + } + return ClanUpdatesHandler.updateClanPlayers(currentClan); + } + +} + +module.exports.StatsUpdateHandler = StatsUpdateHandler; diff --git a/lib/teams/server/event-handlers/stats-update-team-handler.js b/lib/teams/server/event-handlers/stats-update-team-handler.js deleted file mode 100644 index 4d8484b30..000000000 --- a/lib/teams/server/event-handlers/stats-update-team-handler.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * - * Reldens - StatsUpdateTeamHandler - * - */ - -const { TeamUpdatesHandler } = require('../team-updates-handler'); -const { Logger, sc } = require('@reldens/utils'); - -class StatsUpdateTeamHandler -{ - - static async updateTeam(props) - { - let {teamsPlugin, playerSchema} = props; - let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); - if('' === currentTeamId){ - return; - } - let currentTeam = sc.get(teamsPlugin.teams, currentTeamId, false); - if(!currentTeam){ - Logger.error('Team not found: '+ currentTeamId); - return; - } - return TeamUpdatesHandler.updateTeamPlayers(teamsPlugin.teams[playerSchema.currentTeam]); - } - -} - -module.exports.StatsUpdateTeamHandler = StatsUpdateTeamHandler; diff --git a/lib/teams/server/message-actions/try-team-start.js b/lib/teams/server/message-actions/try-team-start.js index a75be59ff..834e7d7a8 100644 --- a/lib/teams/server/message-actions/try-team-start.js +++ b/lib/teams/server/message-actions/try-team-start.js @@ -28,7 +28,7 @@ class TryTeamStart id: playerSchema.sessionId, }; let event = {client, data, room, playerSchema, teamsPlugin, continueStart: true}; - await teamsPlugin.event.emit('reldens.tryTeamStart', event); + await teamsPlugin.events.emit('reldens.tryTeamStart', event); if(!event.continueStart){ return false; } diff --git a/lib/teams/server/models/objection-js/clan-levels-model.js b/lib/teams/server/models/objection-js/clan-levels-model.js index f700a9419..1db40d121 100644 --- a/lib/teams/server/models/objection-js/clan-levels-model.js +++ b/lib/teams/server/models/objection-js/clan-levels-model.js @@ -5,7 +5,6 @@ */ const { ObjectionJsRawModel } = require('@reldens/storage'); -const {PlayersModel} = require("../../../../users/server/models/objection-js/players-model"); class ClanLevelsModel extends ObjectionJsRawModel { diff --git a/lib/teams/server/models/objection-js/clan-model.js b/lib/teams/server/models/objection-js/clan-model.js index f8768ae5d..9a41cb532 100644 --- a/lib/teams/server/models/objection-js/clan-model.js +++ b/lib/teams/server/models/objection-js/clan-model.js @@ -5,7 +5,6 @@ */ const { ObjectionJsRawModel } = require('@reldens/storage'); -const {ClanMembersModel} = require("./clan-members-model"); class ClanModel extends ObjectionJsRawModel { diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 04d652f1b..078b1c864 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -8,7 +8,7 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamsMessageActions } = require('./message-actions'); const { CreatePlayerClanHandler } = require('./event-handlers/create-player-clan-handler'); const { CreatePlayerTeamHandler } = require('./event-handlers/create-player-team-handler'); -const { StatsUpdateTeamHandler } = require('./event-handlers/stats-update-team-handler'); +const { StatsUpdateHandler } = require('./event-handlers/stats-update-handler'); const { EndPlayerHitChangePointTeamHandler } = require('./event-handlers/end-player-hit-change-point-team-handler'); const { Logger, sc } = require('@reldens/utils'); @@ -36,7 +36,8 @@ class TeamsPlugin extends PluginInterface await CreatePlayerTeamHandler.joinExistentTeam(client, playerSchema, this); }); this.events.on('reldens.savePlayerStatsUpdateClient', async (client, playerSchema, room) => { - await StatsUpdateTeamHandler.updateTeam({teamsPlugin: this, playerSchema}); + await StatsUpdateHandler.updateTeam({teamsPlugin: this, playerSchema}); + await StatsUpdateHandler.updateClan({teamsPlugin: this, playerSchema}); }); this.events.on('reldens.endPlayerHitChangePoint', async (event) => { await EndPlayerHitChangePointTeamHandler.savePlayerTeam(event.playerSchema, this); diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 87a91a27d..2f01c7a55 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -11,16 +11,6 @@ class TeamUpdatesHandler { static updateTeamPlayers(team) - { - return this.updatePlayers(team, TeamsConst.ACTIONS.TEAM_UPDATE, TeamsConst.KEY); - } - - static updateClanPlayers(clan) - { - return this.updatePlayers(clan, TeamsConst.ACTIONS.CLAN_UPDATE, TeamsConst.CLAN_KEY); - } - - static updatePlayers(team, act, listener) { let clientsKeys = Object.keys(team.clients); if (0 === clientsKeys.length) { @@ -30,13 +20,13 @@ class TeamUpdatesHandler if (0 === Object.keys(playersList).length) { return false; } - for (let i of clientsKeys) { + for(let i of clientsKeys){ let otherPlayersData = Object.assign({}, playersList); delete otherPlayersData[i]; let sendUpdate = { - act, + act: TeamsConst.ACTIONS.TEAM_UPDATE, id: team.ownerClient.id, - listener, + listener: TeamsConst.KEY, players: otherPlayersData, leaderName: team.owner.playerName, }; @@ -44,6 +34,7 @@ class TeamUpdatesHandler } return true; } + } module.exports.TeamUpdatesHandler = TeamUpdatesHandler; diff --git a/lib/users/client/lifebar-ui.js b/lib/users/client/lifebar-ui.js index d942ddc50..33a27f7eb 100644 --- a/lib/users/client/lifebar-ui.js +++ b/lib/users/client/lifebar-ui.js @@ -296,12 +296,12 @@ class LifebarUi getCurrentTargetId() { - return sc.get(this.gameManager.getCurrentPlayer().currentTarget, 'id', false); + return sc.get(this.gameManager.getCurrentPlayer()?.currentTarget, 'id', false); } getObjectByKey(objectKey) { - return sc.get(this.gameManager.getActiveScene().objectsAnimations, objectKey, false); + return sc.get(this.gameManager.getActiveScene()?.objectsAnimations, objectKey, false); } } diff --git a/package-lock.json b/package-lock.json index 74ceb9a3d..7095a3dfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "@reldens/modifiers": "^0.18.0", "@reldens/skills": "^0.17.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.1", + "@reldens/utils": "^0.19.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", @@ -5471,6 +5471,14 @@ "@reldens/utils": "^0.18.0" } }, + "node_modules/@reldens/items-system/node_modules/@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@reldens/modifiers": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.18.0.tgz", @@ -5479,6 +5487,14 @@ "@reldens/utils": "^0.18.0" } }, + "node_modules/@reldens/modifiers/node_modules/@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@reldens/skills": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.17.0.tgz", @@ -5489,6 +5505,14 @@ "@reldens/utils": "^0.18.0" } }, + "node_modules/@reldens/skills/node_modules/@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@reldens/storage": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", @@ -5502,7 +5526,7 @@ "objection": "^3.0.1" } }, - "node_modules/@reldens/utils": { + "node_modules/@reldens/storage/node_modules/@reldens/utils": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", @@ -5510,6 +5534,14 @@ "await-event-emitter": "^2.0.2" } }, + "node_modules/@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -16405,6 +16437,16 @@ "@reldens/modifiers": "^0.18.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.18.0" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/modifiers": { @@ -16413,6 +16455,16 @@ "integrity": "sha512-j3vLXNRnRLDXildkuGPJUHWre4o5oe1d+novyPWW8bNVgUWJB5HXTorXvjoqT/OOkxfhgZ7xvlNT4Tw7pARRlA==", "requires": { "@reldens/utils": "^0.18.0" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/skills": { @@ -16423,6 +16475,16 @@ "@reldens/modifiers": "^0.18.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.18.0" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/storage": { @@ -16436,12 +16498,22 @@ "knex": "^2.4.1", "mysql": "^2.18.1", "objection": "^3.0.1" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", "requires": { "await-event-emitter": "^2.0.2" } diff --git a/package.json b/package.json index a8c02884b..17a53d718 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@reldens/modifiers": "^0.18.0", "@reldens/skills": "^0.17.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.1", + "@reldens/utils": "^0.19.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", diff --git a/theme/default/index.js b/theme/default/index.js index d446116f7..01ab42ac5 100644 --- a/theme/default/index.js +++ b/theme/default/index.js @@ -6,12 +6,13 @@ * */ +// set logger level and trace: +window.RELDENS_LOG_LEVEL = 9; +window.RELDENS_ENABLE_TRACE_FOR = 'emergency,alert,critical,error,warning'; + const { GameManager } = require('reldens/client'); const { ClientPlugin } = require('../plugins/client-plugin'); const { GameConst } = require('reldens/lib/game/constants'); -// enable logger: -// const { Logger } = require('@reldens/utils'); -// Logger.logLevel = 9; // @TODO - BETA - Move everything from this file as part of the core project and include events to manage the theme. // @TODO - BETA - CLEAN THIS THING ASAP! From 03e15600189445bbe35678dfb629046ce37d50bc Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 20 Mar 2023 21:20:23 +0100 Subject: [PATCH 28/82] - Reldens - v4.0.0 - Clan UI. - Clan migrations. - Teams events emit fix. --- lib/game/client/room-events.js | 1 - lib/game/client/user-interface.js | 6 ++- lib/teams/client/clan-initializer-handler.js | 48 ----------------- lib/teams/client/clan-message-handler.js | 29 +++++++++- lib/teams/client/clan-message-listener.js | 3 ++ lib/teams/client/plugin.js | 53 +++++++++++-------- lib/teams/client/team-target-box-enricher.js | 2 +- lib/teams/constants.js | 1 + .../create-player-clan-handler.js | 10 +++- lib/teams/server/message-actions/team-join.js | 6 +-- .../server/message-actions/team-leave.js | 8 +-- migrations/production/beta.26-sql-update.sql | 6 +++ .../features/teams/templates/ui-clan.html | 9 ++-- theme/default/css/teams.scss | 19 +++++++ theme/default/index.js | 2 +- 15 files changed, 116 insertions(+), 87 deletions(-) delete mode 100644 lib/teams/client/clan-initializer-handler.js diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index e81047a4e..0c95e3371 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -517,7 +517,6 @@ class RoomEvents preloader.load.on('complete', async () => { // set ui on first preloader scene: if(!this.gameEngine.uiScene){ - // assign the preloader: this.gameEngine.uiScene = preloader; // if the box right is present then assign the actions: this.showPlayerName(this.gameManager.playerData.name); diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index 984e09782..6bcfe8ed6 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -51,7 +51,11 @@ class UserInterface this.createBoxContent(uiScene, templateKey, dialogBox); let dialogContainer = gameDom.getElement('.ui-box.ui-dialog-box', dialogBox.node); if(!dialogContainer){ - Logger.critical('Missing dialog container for template key: "'+templateKey+'".'); + Logger.critical('Missing dialog container for template key: "'+templateKey+'".', { + dialogBox, + dialogContainer, + objectElementId + }); return false; } dialogContainer.id = objectElementId; diff --git a/lib/teams/client/clan-initializer-handler.js b/lib/teams/client/clan-initializer-handler.js deleted file mode 100644 index 6d9490deb..000000000 --- a/lib/teams/client/clan-initializer-handler.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * - * Reldens - ClanInitializerHandler - * - */ - -const { UserInterface } = require('../../game/client/user-interface'); -const { TeamsConst } = require('../constants'); -const { sc } = require('@reldens/utils'); - -class ClanInitializerHandler -{ - - static appendCreateUiListener(initialGameData, teamsPlugin) - { - let clanData = sc.get(initialGameData, 'clanData', {}); - if(0 === Object.keys(clanData).length){ - return false; - } - let currentPlayer = teamsPlugin.gameManager.getCurrentPlayer(); - currentPlayer.clan = clanData; - teamsPlugin.events.on('reldens.createUiScene', (preloadScene) => { - this.createClanUi(clanData, teamsPlugin.gameManager, preloadScene); - }); - } - - static createClanUi(clanData, gameManager, preloadScene) - { - let clanUi = sc.get(preloadScene.userInterfaces, clanData.ownerId); - if(clanUi){ - return clanUi; - } - if(!preloadScene.userInterfaces){ - preloadScene.userInterfaces = {}; - } - preloadScene.userInterfaces[clanData.ownerId] = new UserInterface( - gameManager, - {id: clanData.ownerId, type: TeamsConst.KEY, defaultOpen: true, defaultClose: true}, - 'assets/features/teams/templates/ui-clan.html', - TeamsConst.KEY - ); - preloadScene.userInterfaces[clanData.ownerId].createUiElement(preloadScene, TeamsConst.KEY); - return preloadScene.userInterfaces[clanData.ownerId]; - } - -} - -module.exports.ClanInitializerHandler = ClanInitializerHandler; diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 54953a92b..47c9a6d0f 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -5,6 +5,9 @@ */ const { TeamMessageHandler } = require('./team-message-handler'); +const { UserInterface } = require('../../game/client/user-interface'); +const { TeamsConst } = require('../constants'); +const { sc } = require('@reldens/utils'); class ClanMessageHandler extends TeamMessageHandler { @@ -14,6 +17,11 @@ class ClanMessageHandler extends TeamMessageHandler super(props); } + initializeClanUi() + { + let clanUi = this.createClanUi(); + } + showClanRequest() { @@ -21,7 +29,7 @@ class ClanMessageHandler extends TeamMessageHandler showClanBox() { - + let clanUi = this.createClanUi(); } removeClanUi() @@ -29,6 +37,25 @@ class ClanMessageHandler extends TeamMessageHandler } + createClanUi() + { + let clanUi = sc.get(this.uiScene.userInterfaces, TeamsConst.CLAN_KEY); + if (clanUi) { + return clanUi; + } + if (!this.uiScene.userInterfaces) { + this.uiScene.userInterfaces = {}; + } + this.uiScene.userInterfaces[TeamsConst.CLAN_KEY] = new UserInterface( + this.gameManager, + {id: TeamsConst.CLAN_KEY, type: TeamsConst.CLAN_KEY, defaultOpen: true, defaultClose: true}, + 'assets/features/teams/templates/ui-clan.html', + TeamsConst.CLAN_KEY + ); + this.uiScene.userInterfaces[TeamsConst.CLAN_KEY].createUiElement(this.uiScene, TeamsConst.CLAN_KEY); + return this.uiScene.userInterfaces[TeamsConst.CLAN_KEY]; + } + } module.exports.ClanMessageHandler = ClanMessageHandler; diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js index 97598e22b..a8d780a29 100644 --- a/lib/teams/client/clan-message-listener.js +++ b/lib/teams/client/clan-message-listener.js @@ -41,6 +41,9 @@ class ClanMessageListener handleClanMessage(message, clanMessageHandler) { + if(TeamsConst.ACTIONS.CLAN_INITIALIZE === message.act){ + return clanMessageHandler.initializeClanUi(); + } if(TeamsConst.ACTIONS.CLAN_INVITE === message.act){ return clanMessageHandler.showClanRequest(); } diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 9b56dbe84..188f4c376 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -9,10 +9,10 @@ const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); const { ClanMessageListener } = require('./clan-message-listener'); const { TemplatesHandler } = require('./templates-handler'); -const { ClanInitializerHandler } = require('./clan-initializer-handler'); const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); - +const {ClanMessageHandler} = require("./clan-message-handler"); +const {TeamMessageHandler} = require("./team-message-handler"); class TeamsPlugin extends PluginInterface { @@ -29,29 +29,14 @@ class TeamsPlugin extends PluginInterface } let teamMessageListener = new TeamMessageListener(); let clanMessageListener = new ClanMessageListener(); - this.events.on('reldens.beforeCreateEngine', (initialGameData) => { - // ClanInitializerHandler.appendCreateUiListener(initialGameData, this); - }); - this.events.on('reldens.createUiScene', (uiScene) => { - // this.uiManager = new SkillsUi(preloadScene); - // this.uiManager.createUi(); - let roomEvents = uiScene?.gameManager?.activeRoomEvents; + this.events.on('reldens.createEngineScene', (uiScene) => { + let roomEvents = uiScene?.gameManager?.activeRoomEvents || this.gameManager?.activeRoomEvents; if(!roomEvents){ Logger.critical('RoomEvents undefined for process Team messages queue on TeamsPlugin.'); return false; } - if(!sc.isArray(roomEvents.teamMessagesQueue) || 0 === roomEvents.teamMessagesQueue.length){ - return; - } - for(let message of roomEvents.teamMessagesQueue){ - teamMessageListener.handleTeamMessage({roomEvents, message}); - } - if(!sc.isArray(roomEvents.clanMessagesQueue) || 0 === roomEvents.clanMessagesQueue.length){ - return; - } - for(let message of roomEvents.clanMessagesQueue){ - clanMessageListener.handleClanMessage({roomEvents, message}); - } + this.processClanMessagesQueue(roomEvents, clanMessageListener); + this.processTeamMessagesQueue(roomEvents, teamMessageListener); }); this.events.on('reldens.preloadUiScene', (preloadScene) => { TemplatesHandler.preloadTemplates(preloadScene); @@ -70,6 +55,32 @@ class TeamsPlugin extends PluginInterface this.gameManager.config.client.message.listeners[TeamsConst.CLAN_KEY] = clanMessageListener; } + processClanMessagesQueue(roomEvents, clanMessageListener) + { + if(!sc.isArray(roomEvents.clanMessagesQueue)){ + return; + } + if(0 === roomEvents.clanMessagesQueue.length){ + return; + } + for(let message of roomEvents.clanMessagesQueue){ + clanMessageListener.handleClanMessage(message, new ClanMessageHandler({roomEvents, message})); + } + } + + processTeamMessagesQueue(roomEvents, teamMessageListener) + { + if(!sc.isArray(roomEvents.teamMessagesQueue)){ + return; + } + if(0 === roomEvents.teamMessagesQueue.length){ + return; + } + for(let message of roomEvents.teamMessagesQueue){ + teamMessageListener.handleTeamMessage(message, new TeamMessageHandler({roomEvents, message})); + } + } + fetchTeamPlayerBySessionId(sessionId) { let currentTeam = this.gameManager.gameEngine.uiScene.currentTeam; diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/team-target-box-enricher.js index 0039b39fd..09991686f 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -58,7 +58,7 @@ class TeamTargetBoxEnricher // @TODO - BETA - Create translations table with a loader and processor. playerName: targetName, playerId: target.id, - inviteLabel: gameManager.config.get( + inviteLabel: gameManager.config.getWithoutLogs( type+'/titles/inviteLabel', TeamsConst.LABELS[type.toUpperCase()].INVITE_BUTTON_LABEL ) diff --git a/lib/teams/constants.js b/lib/teams/constants.js index a2e4c397a..a50b9b26e 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -20,6 +20,7 @@ module.exports.TeamsConst = { TEAM_UPDATE: pref+'upd', TEAM_LEFT: pref+'lef', TEAM_REMOVE: pref+'rem', + CLAN_INITIALIZE: clanPref+'ini', CLAN_INVITE: clanPref+'inv', CLAN_ACCEPTED: clanPref+'acp', CLAN_LEAVE: clanPref+'lev', diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 4860623ad..ae2962f82 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -7,6 +7,7 @@ const { Clan } = require('../clan'); const { ClanUpdatesHandler } = require('../clan-updates-handler'); const { Logger, sc } = require('@reldens/utils'); +const {TeamsConst} = require("../../constants"); class CreatePlayerClanHandler { @@ -23,7 +24,12 @@ class CreatePlayerClanHandler playerSchema.player_id ); if(!clanMemberModel){ - return false; + let sendData = { + act: TeamsConst.ACTIONS.CLAN_INITIALIZE, + listener: TeamsConst.CLAN_KEY + }; + client.send('*', sendData); + return true; } let clan = await this.loadClan(teamsPlugin, clanMemberModel.clan_id, playerSchema, client, room); if(!clan){ @@ -36,7 +42,7 @@ class CreatePlayerClanHandler if(!endEvent.continueProcess){ return false; } - ClanUpdatesHandler.updateClanPlayers(clan); + return ClanUpdatesHandler.updateClanPlayers(clan); } static async loadClan(teamsPlugin, clanId, playerSchema, client, room) diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index ccf2a1fd8..87c9043de 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -38,14 +38,14 @@ class TeamJoin let currentTeam = teamsPlugin.teams[teamOwnerClient.id]; if(!currentTeam){ let beforeCreateEvent = {teamProps, teamsPlugin, continueBeforeCreate: true}; - await teamsPlugin.event.emit('reldens.beforeTeamCreate', beforeCreateEvent); + await teamsPlugin.events.emit('reldens.beforeTeamCreate', beforeCreateEvent); if(!beforeCreateEvent.continueBeforeCreate){ return false; } currentTeam = new Team(teamProps); } let eventBeforeJoin = {currentTeam, teamsPlugin, continueBeforeJoin: true}; - await teamsPlugin.event.emit('reldens.beforeTeamJoin', eventBeforeJoin); + await teamsPlugin.events.emit('reldens.beforeTeamJoin', eventBeforeJoin); if(!eventBeforeJoin.continueBeforeJoin){ return false; } @@ -54,7 +54,7 @@ class TeamJoin playerSchema.currentTeam = teamOwnerClient.id; teamsPlugin.teams[teamOwnerClient.id] = currentTeam; let eventBeforeJoinUpdate = {currentTeam, teamsPlugin, continueBeforeJoinUpdate: true}; - await teamsPlugin.event.emit('reldens.beforeTeamUpdatePlayers', eventBeforeJoinUpdate); + await teamsPlugin.events.emit('reldens.beforeTeamUpdatePlayers', eventBeforeJoinUpdate); if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ return false; } diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 80eaa0353..4d18c58a1 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -13,7 +13,7 @@ class TeamLeave static async execute(client, data, room, playerSchema, teamsPlugin) { - await teamsPlugin.event.emit('reldens.teamLeave', {client, data, room, playerSchema, teamsPlugin}); + await teamsPlugin.events.emit('reldens.teamLeave', {client, data, room, playerSchema, teamsPlugin}); let teamId = playerSchema.currentTeam; if(!teamId){ return false; @@ -32,7 +32,7 @@ class TeamLeave id: currentTeam.ownerClient.id, listener: TeamsConst.KEY }; - await teamsPlugin.event.emit('reldens.teamLeaveBeforeSendUpdate', { + await teamsPlugin.events.emit('reldens.teamLeaveBeforeSendUpdate', { playerId, sendUpdate, client, @@ -46,7 +46,7 @@ class TeamLeave } if(0 === Object.keys(currentTeam.players).length){ let event = {client, data, room, playerSchema, teamsPlugin, continueDisband: true}; - await teamsPlugin.event.emit('reldens.beforeTeamDisband', event); + await teamsPlugin.events.emit('reldens.beforeTeamDisband', event); if(!event.continueDisband){ return false; } @@ -54,7 +54,7 @@ class TeamLeave return true; } let event = {client, data, room, playerSchema, teamsPlugin, continueLeave: true}; - await teamsPlugin.event.emit('reldens.beforeTeamDisband', event); + await teamsPlugin.events.emit('reldens.beforeTeamDisband', event); if(!event.continueLeave){ return false; } diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 1979baf19..6505d22f0 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -47,6 +47,12 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/enabled', '1', @boolean_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/responsiveX', '100', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/responsiveY', '0', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/x', '430', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/y', '100', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/controls/allowPrimaryTouch', '1', @boolean_id); # Features: diff --git a/theme/default/assets/features/teams/templates/ui-clan.html b/theme/default/assets/features/teams/templates/ui-clan.html index 0356c838b..a66946a96 100644 --- a/theme/default/assets/features/teams/templates/ui-clan.html +++ b/theme/default/assets/features/teams/templates/ui-clan.html @@ -1,5 +1,6 @@ -
+
- close -
+
+ close +
{{title}}
+
{{content}}
-open +open diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index 5b6387b92..f6c538234 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -11,12 +11,22 @@ right: 360px; } +.clan-dialog-box { + min-width: 160px; + top: 250px; + right: 360px; +} + @media (max-height: 400px) { .teams-dialog-box { top: 50px; } + .clan-dialog-box { + top: 80px; + } + } .teams-open { @@ -28,6 +38,15 @@ max-width: 40px; } +.clan-open { + position: relative; + top: 280px; + right: 60px; + background: rgba(0, 0, 0, 0.5); + padding: 10px; + max-width: 40px; +} + .team-player, .property-box, .properties-list-container { diff --git a/theme/default/index.js b/theme/default/index.js index 01ab42ac5..fd9948e5e 100644 --- a/theme/default/index.js +++ b/theme/default/index.js @@ -6,7 +6,7 @@ * */ -// set logger level and trace: +// set logger level and trace, this needs to be specified before the game manager is required: window.RELDENS_LOG_LEVEL = 9; window.RELDENS_ENABLE_TRACE_FOR = 'emergency,alert,critical,error,warning'; From e82497b5ea46584d9f9cde64afd19badfe2158be Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 21 Mar 2023 19:55:05 +0100 Subject: [PATCH 29/82] - Reldens - v4.0.0 - Clan create UI. --- lib/game/client/scene-preloader.js | 1 + lib/game/client/user-interface.js | 1 + lib/teams/client/clan-message-handler.js | 73 ++++++++++++++++++- lib/teams/client/messages-processor.js | 44 +++++++++++ lib/teams/client/plugin.js | 41 ++--------- lib/teams/constants.js | 6 ++ lib/teams/server/clan-message-actions.js | 47 ++++++++++++ .../server/message-actions/clan-create.js | 18 +++++ lib/teams/server/plugin.js | 6 +- ...age-actions.js => team-message-actions.js} | 6 +- .../features/teams/templates/clan-create.html | 3 +- theme/default/assets/html/ui-loading.html | 3 + theme/default/css/chat.scss | 1 + theme/default/css/teams.scss | 14 +++- 14 files changed, 218 insertions(+), 46 deletions(-) create mode 100644 lib/teams/client/messages-processor.js create mode 100644 lib/teams/server/clan-message-actions.js create mode 100644 lib/teams/server/message-actions/clan-create.js rename lib/teams/server/{message-actions.js => team-message-actions.js} (91%) create mode 100644 theme/default/assets/html/ui-loading.html diff --git a/lib/game/client/scene-preloader.js b/lib/game/client/scene-preloader.js index 8a7300347..2bc589114 100644 --- a/lib/game/client/scene-preloader.js +++ b/lib/game/client/scene-preloader.js @@ -111,6 +111,7 @@ class ScenePreloader extends Scene this.load.html('uiOptionButton', 'assets/html/ui-option-button.html'); this.load.html('uiOptionIcon', 'assets/html/ui-option-icon.html'); this.load.html('uiOptionsContainer', 'assets/html/ui-options-container.html'); + this.load.html('uiLoading', 'assets/html/ui-loading.html'); this.eventsManager.emitSync('reldens.preloadUiScene', this); } diff --git a/lib/game/client/user-interface.js b/lib/game/client/user-interface.js index 6bcfe8ed6..37347b900 100644 --- a/lib/game/client/user-interface.js +++ b/lib/game/client/user-interface.js @@ -64,6 +64,7 @@ class UserInterface this.activateCloseButton(dialogBox, dialogContainer, openButton, uiScene, gameDom); uiScene.userInterfaces[this.id] = this; uiScene.elementsUi[this.id] = dialogBox; + // @TODO - BETA - refactor to return the created dialog box. return this; } diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 47c9a6d0f..dfa3fb6e0 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -7,7 +7,7 @@ const { TeamMessageHandler } = require('./team-message-handler'); const { UserInterface } = require('../../game/client/user-interface'); const { TeamsConst } = require('../constants'); -const { sc } = require('@reldens/utils'); +const { sc, Logger} = require('@reldens/utils'); class ClanMessageHandler extends TeamMessageHandler { @@ -20,6 +20,23 @@ class ClanMessageHandler extends TeamMessageHandler initializeClanUi() { let clanUi = this.createClanUi(); + let title = this.gameManager.config.getWithoutLogs( + 'client/clan/labels/createClanTitle', + TeamsConst.LABELS.CLAN.CREATE_CLAN_TITLE + ); + let container = this.gameManager.gameDom.getElement('.clan-dialog-box .box-content'); + if(!container){ + Logger.error('Missing container: "#box-clan .box-content".'); + return false; + } + let uiBox = this.uiScene.elementsUi['clan']; + if(!uiBox){ + Logger.error('Clan UI box not found.', {clanUi, container, uiBox}); + return false; + } + this.roomEvents.uiSetTitle(uiBox, {title}); + this.roomEvents.uiSetContent(uiBox, {content: this.createClanContent()}, this.uiScene); + this.activateCreateButton(); } showClanRequest() @@ -40,10 +57,10 @@ class ClanMessageHandler extends TeamMessageHandler createClanUi() { let clanUi = sc.get(this.uiScene.userInterfaces, TeamsConst.CLAN_KEY); - if (clanUi) { + if(clanUi){ return clanUi; } - if (!this.uiScene.userInterfaces) { + if(!this.uiScene.userInterfaces){ this.uiScene.userInterfaces = {}; } this.uiScene.userInterfaces[TeamsConst.CLAN_KEY] = new UserInterface( @@ -56,6 +73,56 @@ class ClanMessageHandler extends TeamMessageHandler return this.uiScene.userInterfaces[TeamsConst.CLAN_KEY]; } + createClanContent() + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let templateContent = this.uiScene.cache.html.get('clanCreate'); + if(!templateContent){ + Logger.error('Missing template "clanCreate".'); + return ''; + } + let templateParams = { + playerId: this.gameManager.getCurrentPlayer().playerId, + createLabel: this.gameManager.config.getWithoutLogs( + 'client/clan/labels/createLabel', + TeamsConst.LABELS.CLAN.CREATE + ), + clanNamePlaceholder: this.gameManager.config.getWithoutLogs( + 'client/clan/labels/namePlaceholder', + TeamsConst.LABELS.CLAN.NAME_PLACEHOLDER + ) + }; + return this.gameManager.gameEngine.parseTemplate(templateContent, templateParams); + } + + activateCreateButton() + { + let createButton = this.gameManager.gameDom.getElement('.clan-dialog-box .submit-clan-create'); + if(!createButton){ + Logger.warning('Clan create button not found by ".clan-dialog-box .clan-create".'); + return false; + } + let nameInput = this.gameManager.gameDom.getElement('.clan-dialog-box .clan-name-input'); + if(!nameInput){ + Logger.warning('Clan create button not found by ".clan-dialog-box .clan-name-input".'); + return false; + } + createButton.addEventListener('click', () => { + if(0 === nameInput.value.length){ + return false; + } + this.gameManager.gameDom.updateContent( + '.clan-dialog-box .box-content', + this.uiScene.cache.html.get('uiLoading') + ); + this.gameManager.activeRoomEvents.room.send('*', { + act: TeamsConst.ACTIONS.CLAN_CREATE, + [TeamsConst.ACTIONS.CLAN_NAME]: nameInput.value, + id: this.message.id + }); + }); + } + } module.exports.ClanMessageHandler = ClanMessageHandler; diff --git a/lib/teams/client/messages-processor.js b/lib/teams/client/messages-processor.js new file mode 100644 index 000000000..b0575adfe --- /dev/null +++ b/lib/teams/client/messages-processor.js @@ -0,0 +1,44 @@ +/** + * + * Reldens - MessagesProcessor + * + */ + +const { ClanMessageHandler } = require('./clan-message-handler'); +const { TeamMessageHandler } = require('./team-message-handler'); +const { sc } = require('@reldens/utils'); + +class MessagesProcessor +{ + + static processClanMessagesQueue(roomEvents, clanMessageListener) + { + if(!sc.isArray(roomEvents.clanMessagesQueue)){ + return; + } + if(0 === roomEvents.clanMessagesQueue.length){ + return; + } + for(let message of roomEvents.clanMessagesQueue){ + clanMessageListener.handleClanMessage(message, new ClanMessageHandler({roomEvents, message})); + } + roomEvents.clanMessagesQueue = []; + } + + static processTeamMessagesQueue(roomEvents, teamMessageListener) + { + if(!sc.isArray(roomEvents.teamMessagesQueue)){ + return; + } + if(0 === roomEvents.teamMessagesQueue.length){ + return; + } + for(let message of roomEvents.teamMessagesQueue){ + teamMessageListener.handleTeamMessage(message, new TeamMessageHandler({roomEvents, message})); + } + roomEvents.teamMessagesQueue = []; + } + +} + +module.exports.MessageProcessor = MessagesProcessor; diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index 188f4c376..e87caf42b 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -8,11 +8,10 @@ const { PluginInterface } = require('../../features/plugin-interface'); const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); const { ClanMessageListener } = require('./clan-message-listener'); +const { MessageProcessor } = require('./messages-processor'); const { TemplatesHandler } = require('./templates-handler'); const { TeamsConst } = require('../constants'); const { Logger, sc } = require('@reldens/utils'); -const {ClanMessageHandler} = require("./clan-message-handler"); -const {TeamMessageHandler} = require("./team-message-handler"); class TeamsPlugin extends PluginInterface { @@ -27,16 +26,16 @@ class TeamsPlugin extends PluginInterface if (!this.events) { Logger.error('EventsManager undefined in TeamsPlugin.'); } - let teamMessageListener = new TeamMessageListener(); - let clanMessageListener = new ClanMessageListener(); + this.teamMessageListener = new TeamMessageListener(); + this.clanMessageListener = new ClanMessageListener(); this.events.on('reldens.createEngineScene', (uiScene) => { let roomEvents = uiScene?.gameManager?.activeRoomEvents || this.gameManager?.activeRoomEvents; if(!roomEvents){ Logger.critical('RoomEvents undefined for process Team messages queue on TeamsPlugin.'); return false; } - this.processClanMessagesQueue(roomEvents, clanMessageListener); - this.processTeamMessagesQueue(roomEvents, teamMessageListener); + MessageProcessor.processClanMessagesQueue(roomEvents, this.clanMessageListener); + MessageProcessor.processTeamMessagesQueue(roomEvents, this.teamMessageListener); }); this.events.on('reldens.preloadUiScene', (preloadScene) => { TemplatesHandler.preloadTemplates(preloadScene); @@ -51,34 +50,8 @@ class TeamsPlugin extends PluginInterface // this.messagesHandler.processOrQueueMessage(message); // }); // }); - this.gameManager.config.client.message.listeners[TeamsConst.KEY] = teamMessageListener; - this.gameManager.config.client.message.listeners[TeamsConst.CLAN_KEY] = clanMessageListener; - } - - processClanMessagesQueue(roomEvents, clanMessageListener) - { - if(!sc.isArray(roomEvents.clanMessagesQueue)){ - return; - } - if(0 === roomEvents.clanMessagesQueue.length){ - return; - } - for(let message of roomEvents.clanMessagesQueue){ - clanMessageListener.handleClanMessage(message, new ClanMessageHandler({roomEvents, message})); - } - } - - processTeamMessagesQueue(roomEvents, teamMessageListener) - { - if(!sc.isArray(roomEvents.teamMessagesQueue)){ - return; - } - if(0 === roomEvents.teamMessagesQueue.length){ - return; - } - for(let message of roomEvents.teamMessagesQueue){ - teamMessageListener.handleTeamMessage(message, new TeamMessageHandler({roomEvents, message})); - } + this.gameManager.config.client.message.listeners[TeamsConst.KEY] = this.teamMessageListener; + this.gameManager.config.client.message.listeners[TeamsConst.CLAN_KEY] = this.clanMessageListener; } fetchTeamPlayerBySessionId(sessionId) diff --git a/lib/teams/constants.js b/lib/teams/constants.js index a50b9b26e..1890791be 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -12,6 +12,7 @@ module.exports.TeamsConst = { CLAN_KEY: 'clan', TEAM_PREF: pref, CLAN_PREF: clanPref, + NAME_LIMIT: 50, ACTIONS: { // @TODO - BETA - Standardize generic actions and use dots to split, like UPDATE = '.up', REMOVE = '.rm', etc. TEAM_INVITE: pref+'inv', @@ -21,12 +22,14 @@ module.exports.TeamsConst = { TEAM_LEFT: pref+'lef', TEAM_REMOVE: pref+'rem', CLAN_INITIALIZE: clanPref+'ini', + CLAN_CREATE: clanPref+'new', CLAN_INVITE: clanPref+'inv', CLAN_ACCEPTED: clanPref+'acp', CLAN_LEAVE: clanPref+'lev', CLAN_UPDATE: clanPref+'upd', CLAN_LEFT: clanPref+'lef', CLAN_REMOVE: clanPref+'rem', + CLAN_NAME: clanPref+'nam', }, LABELS: { TEAM: { @@ -38,9 +41,12 @@ module.exports.TeamsConst = { PROPERTY_MAX_VALUE: '/ %propertyMaxValue' }, CLAN: { + CREATE_CLAN_TITLE: 'Clan - Creation', INVITE_BUTTON_LABEL: 'Clan - Invite', REQUEST_FROM: 'Accept clan request from:', LEADER_NAME_TITLE: 'Clan leader: %leaderName', + NAME_PLACEHOLDER: 'Choose a clan name...', + CREATE: 'Create', DISBAND: 'Disband Clan', LEAVE: 'Leave Clan', PROPERTY_MAX_VALUE: '/ %propertyMaxValue' diff --git a/lib/teams/server/clan-message-actions.js b/lib/teams/server/clan-message-actions.js new file mode 100644 index 000000000..849ace5d8 --- /dev/null +++ b/lib/teams/server/clan-message-actions.js @@ -0,0 +1,47 @@ +/** + * + * Reldens - ClanMessageActions + * + */ + +const { ClanCreate } = require('./message-actions/clan-create'); +const { TeamsConst } = require('../constants'); +const { sc } = require('@reldens/utils'); + +class ClanMessageActions +{ + + constructor(props) + { + this.teamsPlugin = props.teamsPlugin; + } + + async executeMessageActions(client, data, room, playerSchema) + { + if(!sc.hasOwn(data, 'act')){ + return false; + } + if(0 !== data.act.indexOf(TeamsConst.CLAN_PREF)){ + return false; + } + if(TeamsConst.ACTIONS.CLAN_INVITE === data.act){ + // await TryTeamStart.execute(client, data, room, playerSchema, this.teamsPlugin); + return true; + } + if(TeamsConst.ACTIONS.CLAN_CREATE === data.act){ + await ClanCreate.execute(client, data, room, playerSchema, this.teamsPlugin); + return true; + } + if(TeamsConst.ACTIONS.CLAN_ACCEPTED === data.act && '1' === data.value){ + // await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); + return true; + } + if(TeamsConst.ACTIONS.CLAN_LEAVE === data.act){ + // await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); + return true; + } + } + +} + +module.exports.ClanMessageActions = ClanMessageActions; diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js new file mode 100644 index 000000000..d84d4423c --- /dev/null +++ b/lib/teams/server/message-actions/clan-create.js @@ -0,0 +1,18 @@ +/** + * + * Reldens - ClanMessageActions + * + */ +const {TeamsConst} = require("../../constants"); + +class ClanCreate +{ + static async execute(client, data, room, playerSchema, teamsPlugin) + { + let characterLimit = TeamsConst.NAME_LIMIT; + let clanName = data[TeamsConst.ACTIONS.CLAN_NAME]?.toString().replace(/\\/g, '').substring(0, characterLimit); + // @TODO - Finish. + } +} + +module.exports.ClanCreate = ClanCreate; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 078b1c864..59ca5f8fd 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -5,7 +5,8 @@ */ const { PluginInterface } = require('../../features/plugin-interface'); -const { TeamsMessageActions } = require('./message-actions'); +const { ClanMessageActions } = require('./clan-message-actions'); +const { TeamMessageActions } = require('./team-message-actions'); const { CreatePlayerClanHandler } = require('./event-handlers/create-player-clan-handler'); const { CreatePlayerTeamHandler } = require('./event-handlers/create-player-team-handler'); const { StatsUpdateHandler } = require('./event-handlers/stats-update-handler'); @@ -29,7 +30,8 @@ class TeamsPlugin extends PluginInterface this.clans = sc.get(props, 'clans', {}); this.changingRoomPlayers = sc.get(props, 'changingRoomPlayers', {}); this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { - roomMessageActions.teams = new TeamsMessageActions({teamsPlugin: this}); + roomMessageActions.teams = new TeamMessageActions({teamsPlugin: this}); + roomMessageActions.clan = new ClanMessageActions({teamsPlugin: this}); }); this.events.on('reldens.createPlayerAfter', async (client, authResult, playerSchema, room) => { await CreatePlayerClanHandler.enrichPlayerWithClan(client, playerSchema, room, this); diff --git a/lib/teams/server/message-actions.js b/lib/teams/server/team-message-actions.js similarity index 91% rename from lib/teams/server/message-actions.js rename to lib/teams/server/team-message-actions.js index 8df340577..b79bd0e47 100644 --- a/lib/teams/server/message-actions.js +++ b/lib/teams/server/team-message-actions.js @@ -1,6 +1,6 @@ /** * - * Reldens - TeamsMessageActions + * Reldens - TeamMessageActions * */ @@ -10,7 +10,7 @@ const { TeamLeave } = require('./message-actions/team-leave'); const { TeamsConst } = require('../constants'); const { sc } = require('@reldens/utils'); -class TeamsMessageActions +class TeamMessageActions { constructor(props) @@ -42,4 +42,4 @@ class TeamsMessageActions } -module.exports.TeamsMessageActions = TeamsMessageActions; +module.exports.TeamMessageActions = TeamMessageActions; diff --git a/theme/default/assets/features/teams/templates/clan-create.html b/theme/default/assets/features/teams/templates/clan-create.html index d2677bb6a..69935b9c1 100644 --- a/theme/default/assets/features/teams/templates/clan-create.html +++ b/theme/default/assets/features/teams/templates/clan-create.html @@ -1,3 +1,4 @@
- + +
diff --git a/theme/default/assets/html/ui-loading.html b/theme/default/assets/html/ui-loading.html new file mode 100644 index 000000000..672abeb84 --- /dev/null +++ b/theme/default/assets/html/ui-loading.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/theme/default/css/chat.scss b/theme/default/css/chat.scss index c04b7725c..60423f954 100644 --- a/theme/default/css/chat.scss +++ b/theme/default/css/chat.scss @@ -27,6 +27,7 @@ #chat-input { width: 80%; + outline: none; } #chat-send { diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index f6c538234..beedb02c3 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -15,6 +15,14 @@ min-width: 160px; top: 250px; right: 360px; + + .clan-name-input { + outline: none; + } + + .default-loading-container img{ + max-width: 48px; + } } @media (max-height: 400px) { @@ -29,7 +37,7 @@ } -.teams-open { +.clan-open { position: relative; top: 230px; right: 60px; @@ -38,9 +46,9 @@ max-width: 40px; } -.clan-open { +.teams-open { position: relative; - top: 280px; + top: 300px; right: 60px; background: rgba(0, 0, 0, 0.5); padding: 10px; From 8f2056e5a9462170486930b96cb88f48240b9ed6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 24 Mar 2023 20:23:25 +0100 Subject: [PATCH 30/82] - Reldens - v4.0.0 - Clan create on server. --- lib/teams/client/clan-message-handler.js | 5 + lib/teams/client/clan-message-listener.js | 3 + lib/teams/constants.js | 9 +- lib/teams/server/clan-updates-handler.js | 1 + .../create-player-clan-handler.js | 2 +- .../server/message-actions/clan-create.js | 50 +++++++- lib/teams/server/team-updates-handler.js | 14 ++- migrations/production/beta.26-sql-update.sql | 45 +++---- package-lock.json | 116 +++++------------- package.json | 6 +- 10 files changed, 135 insertions(+), 116 deletions(-) diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index dfa3fb6e0..5fdb8f466 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -39,6 +39,11 @@ class ClanMessageHandler extends TeamMessageHandler this.activateCreateButton(); } + showNewClan() + { + + } + showClanRequest() { diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js index a8d780a29..306af7a37 100644 --- a/lib/teams/client/clan-message-listener.js +++ b/lib/teams/client/clan-message-listener.js @@ -44,6 +44,9 @@ class ClanMessageListener if(TeamsConst.ACTIONS.CLAN_INITIALIZE === message.act){ return clanMessageHandler.initializeClanUi(); } + if(TeamsConst.ACTIONS.CLAN_CREATE === message.act){ + return clanMessageHandler.showNewClan(); + } if(TeamsConst.ACTIONS.CLAN_INVITE === message.act){ return clanMessageHandler.showClanRequest(); } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 1890791be..087468be2 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -13,6 +13,13 @@ module.exports.TeamsConst = { TEAM_PREF: pref, CLAN_PREF: clanPref, NAME_LIMIT: 50, + CLAN_STARTING_POINTS: 1, + VALIDATION: { + SUCCESS: 1, + NAME_EXISTS: 2, + LEVEL_ISSUE: 3, + CREATE_ERROR: 4 + }, ACTIONS: { // @TODO - BETA - Standardize generic actions and use dots to split, like UPDATE = '.up', REMOVE = '.rm', etc. TEAM_INVITE: pref+'inv', @@ -29,7 +36,7 @@ module.exports.TeamsConst = { CLAN_UPDATE: clanPref+'upd', CLAN_LEFT: clanPref+'lef', CLAN_REMOVE: clanPref+'rem', - CLAN_NAME: clanPref+'nam', + CLAN_NAME: clanPref+'nam' }, LABELS: { TEAM: { diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 62cf3e51e..6a66b1d36 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -12,6 +12,7 @@ class ClanUpdatesHandler static updateClanPlayers(clan) { + // @TODO - BETA - Consider extend TeamUpdatesHandler. let clientsKeys = Object.keys(clan.clients); if (0 === clientsKeys.length) { return false; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index ae2962f82..62cd72ea0 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -6,8 +6,8 @@ const { Clan } = require('../clan'); const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { TeamsConst } = require('../../constants'); const { Logger, sc } = require('@reldens/utils'); -const {TeamsConst} = require("../../constants"); class CreatePlayerClanHandler { diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index d84d4423c..db8aaf73b 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -3,15 +3,59 @@ * Reldens - ClanMessageActions * */ -const {TeamsConst} = require("../../constants"); + +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { Clan } = require('../clan'); +const { TeamsConst } = require('../../constants'); class ClanCreate { static async execute(client, data, room, playerSchema, teamsPlugin) { - let characterLimit = TeamsConst.NAME_LIMIT; + let characterLimit = room.config.getWithoutLogs('server/clan/settings/nameLimit', TeamsConst.NAME_LIMIT); let clanName = data[TeamsConst.ACTIONS.CLAN_NAME]?.toString().replace(/\\/g, '').substring(0, characterLimit); - // @TODO - Finish. + let repository = teamsPlugin.dataServer.getEntity('clan'); + let exists = await repository.loadOneBy('name', clanName); + if(exists){ + client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.NAME_EXISTS}); + return; + } + let levelsRepository = teamsPlugin.dataServer.getEntity('clanLevels'); + levelsRepository.limit = 1; + levelsRepository.sortBy = 'key'; + let firstLevel = await levelsRepository.loadOne({}); + if(!firstLevel){ + client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.LEVEL_ISSUE}); + return; + } + levelsRepository.limit = 0; + levelsRepository.sortBy = false; + let createdClan = await repository.create({ + name: clanName, + owner_id: playerSchema.player_id, + points: room.config.getWithoutLogs('server/clan/settings/startingPoints', TeamsConst.CLAN_STARTING_POINTS), + level: firstLevel.key + }); + if(!createdClan){ + client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.CREATE_ERROR}); + return; + } + client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.SUCCESS}); + /* + let ownerMember = await teamsPlugin.dataServer.getEntity('clanMembers').create({ + clan_id: createdClan.id, + player_id: playerSchema.player_id + }); + ownerMember.parent_player = await teamsPlugin.dataServer.getEntity('players').loadById(playerSchema.player_id); + createdClan.members = [ownerMember]; + let newClan = Clan.fromModel({ + clanModel: createdClan, + owner: playerSchema, + ownerClient: client, + sharedProperties: room.config.get('client/ui/teams/sharedProperties') + }); + ClanUpdatesHandler.updateClanPlayers(newClan); + */ } } diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index 2f01c7a55..cb0bb1f80 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -24,9 +24,9 @@ class TeamUpdatesHandler let otherPlayersData = Object.assign({}, playersList); delete otherPlayersData[i]; let sendUpdate = { - act: TeamsConst.ACTIONS.TEAM_UPDATE, + act: this.actionConstant(), id: team.ownerClient.id, - listener: TeamsConst.KEY, + listener: this.listenerKey(), players: otherPlayersData, leaderName: team.owner.playerName, }; @@ -35,6 +35,16 @@ class TeamUpdatesHandler return true; } + static listenerKey() + { + return TeamsConst.KEY; + } + + static actionConstant() + { + return TeamsConst.ACTIONS.TEAM_UPDATE; + } + } module.exports.TeamUpdatesHandler = TeamUpdatesHandler; diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 6505d22f0..bba0e0682 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -32,7 +32,7 @@ UPDATE `config` SET `type` = @json_id WHERE `type` = 'j'; UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; -ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; +ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; # Config: INSERT INTO `config` VALUES (NULL, 'client', 'general/gameEngine/updateGameSizeTimeOut', '500', @float_id); @@ -64,11 +64,14 @@ CREATE TABLE `clan` ( `owner_id` INT(10) UNSIGNED NOT NULL, `name` VARCHAR(50) NOT NULL DEFAULT '' COLLATE 'utf8_unicode_ci', `points` INT(10) UNSIGNED NOT NULL DEFAULT '0', - `level` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `level` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `owner_id` (`owner_id`) USING BTREE, - UNIQUE INDEX `name` (`name`) USING BTREE -) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + UNIQUE INDEX `name` (`name`) USING BTREE, + INDEX `FK_clan_clan_levels` (`level`) USING BTREE, + CONSTRAINT `FK_clan_clan_levels` FOREIGN KEY (`level`) REFERENCES `clan_levels` (`key`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `FK_clan_players` FOREIGN KEY (`owner_id`) REFERENCES `players` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; CREATE TABLE `clan_members` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -82,29 +85,29 @@ CREATE TABLE `clan_members` ( CREATE TABLE `clan_levels` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `key` INT(10) UNSIGNED NOT NULL, - `label` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `label` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci', `required_experience` BIGINT(20) UNSIGNED NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE -) COLLATE='utf8mb3_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; CREATE TABLE `clan_levels_modifiers` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `level_id` INT(10) UNSIGNED NOT NULL, - `key` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', - `property_key` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', + `key` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci', + `property_key` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci', `operation` INT(10) UNSIGNED NOT NULL, - `value` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_unicode_ci', - `minValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', - `maxValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', - `minProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', - `maxProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb3_unicode_ci', + `value` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci', + `minValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', + `maxValue` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', + `minProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', + `maxProperty` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', PRIMARY KEY (`id`) USING BTREE, INDEX `modifier_id` (`key`) USING BTREE, INDEX `level_key` (`level_id`) USING BTREE, INDEX `FK_clan_levels_modifiers_operation_types` (`operation`) USING BTREE, CONSTRAINT `FK_clan_levels_modifiers_operation_types` FOREIGN KEY (`operation`) REFERENCES `operation_types` (`key`) ON UPDATE NO ACTION ON DELETE NO ACTION, - CONSTRAINT `FK_clan_levels_modifiers_clan_levels` FOREIGN KEY (`level_id`) REFERENCES `clan_levels` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION -) COLLATE='utf8mb3_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; + CONSTRAINT `FK_clan_levels_modifiers_clan_levels` FOREIGN KEY (`level_id`) REFERENCES `clan_levels` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=1; # Inventory tables fix: ALTER TABLE `items_inventory` DROP FOREIGN KEY `FK_items_inventory_items_item`; @@ -129,10 +132,10 @@ ALTER TABLE `objects_items_inventory` ALTER TABLE `features` CHANGE COLUMN `is_enabled` `is_enabled` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `title`; -ALTER TABLE `items_inventory` ADD CONSTRAINT `FK_items_inventory_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; -ALTER TABLE `items_item` ADD CONSTRAINT `FK_items_item_items_group` FOREIGN KEY (`group_id`) REFERENCES `items_group` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; -ALTER TABLE `items_item_modifiers` ADD CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; -ALTER TABLE `objects_items_inventory` ADD CONSTRAINT `FK_objects_items_inventory_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION; +ALTER TABLE `items_inventory` ADD CONSTRAINT `FK_items_inventory_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; +ALTER TABLE `items_item` ADD CONSTRAINT `FK_items_item_items_group` FOREIGN KEY (`group_id`) REFERENCES `items_group` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; +ALTER TABLE `items_item_modifiers` ADD CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; +ALTER TABLE `objects_items_inventory` ADD CONSTRAINT `FK_objects_items_inventory_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; # Rewards: CREATE TABLE `rewards` ( @@ -146,8 +149,8 @@ CREATE TABLE `rewards` ( PRIMARY KEY (`id`) USING BTREE, INDEX `FK_rewards_items_item` (`item_id`) USING BTREE, INDEX `FK_rewards_objects` (`object_id`) USING BTREE, - CONSTRAINT `FK_rewards_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION, - CONSTRAINT `FK_rewards_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION + CONSTRAINT `FK_rewards_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `FK_rewards_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; ####################################################################################################################### diff --git a/package-lock.json b/package-lock.json index 7095a3dfc..c354ae935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,9 +53,9 @@ "@parcel/transformer-svg": "2.8.0", "@parcel/transformer-webmanifest": "2.8.0", "@parcel/transformer-worklet": "2.8.0", - "@reldens/items-system": "^0.17.0", - "@reldens/modifiers": "^0.18.0", - "@reldens/skills": "^0.17.0", + "@reldens/items-system": "^0.18.0", + "@reldens/modifiers": "^0.19.0", + "@reldens/skills": "^0.18.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.19.0", "adminjs": "5.7.3", @@ -5462,55 +5462,31 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@reldens/items-system": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.17.0.tgz", - "integrity": "sha512-Emtish79IpdHFFTwj5HTliMi4kYEVr0iVo+ks15nHzE32p8/o9aERDVF7AGMotKFLA1FWdFfoqdGnbGf24+vPw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.0.tgz", + "integrity": "sha512-P7ruOj7JUwi8vSeg211zLMGXnDHto4wVSjJLH50cRuxOlrJq1eiuwUTkx66wyZopZSQ7mD3u9mbpaAYVoSoWBw==", "dependencies": { - "@reldens/modifiers": "^0.18.0", + "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0" - } - }, - "node_modules/@reldens/items-system/node_modules/@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", - "dependencies": { - "await-event-emitter": "^2.0.2" + "@reldens/utils": "^0.19.0" } }, "node_modules/@reldens/modifiers": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.18.0.tgz", - "integrity": "sha512-j3vLXNRnRLDXildkuGPJUHWre4o5oe1d+novyPWW8bNVgUWJB5HXTorXvjoqT/OOkxfhgZ7xvlNT4Tw7pARRlA==", - "dependencies": { - "@reldens/utils": "^0.18.0" - } - }, - "node_modules/@reldens/modifiers/node_modules/@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.0.tgz", + "integrity": "sha512-tJyHi7NIOpANh+8lBmq7GpD5xu6L2tGutvfd8+RaliFy5BMXzwBM7ce9+tKd3Ja5diUi5WMA8w9xKT5lZ+327w==", "dependencies": { - "await-event-emitter": "^2.0.2" + "@reldens/utils": "^0.19.0" } }, "node_modules/@reldens/skills": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.17.0.tgz", - "integrity": "sha512-BkNT5PDQq3qzAlb9DUcgBXZCRcgIZ3hlsG1Zfx1sTiq/9t2XhrVVkPH2SzL7kNTdxuOgFlX6N/KNlV8LmDD4mg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.18.0.tgz", + "integrity": "sha512-pHPeF+1W7GJyUp0ZJ4lshBNbV+ec6uyW24KMequIhfmvQnRmBbhplV1VjRkqFBDiKLKHjbO4tSBzC0nj0UWoeg==", "dependencies": { - "@reldens/modifiers": "^0.18.0", + "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0" - } - }, - "node_modules/@reldens/skills/node_modules/@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", - "dependencies": { - "await-event-emitter": "^2.0.2" + "@reldens/utils": "^0.19.0" } }, "node_modules/@reldens/storage": { @@ -16430,61 +16406,31 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@reldens/items-system": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.17.0.tgz", - "integrity": "sha512-Emtish79IpdHFFTwj5HTliMi4kYEVr0iVo+ks15nHzE32p8/o9aERDVF7AGMotKFLA1FWdFfoqdGnbGf24+vPw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.0.tgz", + "integrity": "sha512-P7ruOj7JUwi8vSeg211zLMGXnDHto4wVSjJLH50cRuxOlrJq1eiuwUTkx66wyZopZSQ7mD3u9mbpaAYVoSoWBw==", "requires": { - "@reldens/modifiers": "^0.18.0", + "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0" - }, - "dependencies": { - "@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", - "requires": { - "await-event-emitter": "^2.0.2" - } - } + "@reldens/utils": "^0.19.0" } }, "@reldens/modifiers": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.18.0.tgz", - "integrity": "sha512-j3vLXNRnRLDXildkuGPJUHWre4o5oe1d+novyPWW8bNVgUWJB5HXTorXvjoqT/OOkxfhgZ7xvlNT4Tw7pARRlA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.0.tgz", + "integrity": "sha512-tJyHi7NIOpANh+8lBmq7GpD5xu6L2tGutvfd8+RaliFy5BMXzwBM7ce9+tKd3Ja5diUi5WMA8w9xKT5lZ+327w==", "requires": { - "@reldens/utils": "^0.18.0" - }, - "dependencies": { - "@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", - "requires": { - "await-event-emitter": "^2.0.2" - } - } + "@reldens/utils": "^0.19.0" } }, "@reldens/skills": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.17.0.tgz", - "integrity": "sha512-BkNT5PDQq3qzAlb9DUcgBXZCRcgIZ3hlsG1Zfx1sTiq/9t2XhrVVkPH2SzL7kNTdxuOgFlX6N/KNlV8LmDD4mg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.18.0.tgz", + "integrity": "sha512-pHPeF+1W7GJyUp0ZJ4lshBNbV+ec6uyW24KMequIhfmvQnRmBbhplV1VjRkqFBDiKLKHjbO4tSBzC0nj0UWoeg==", "requires": { - "@reldens/modifiers": "^0.18.0", + "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.18.0" - }, - "dependencies": { - "@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", - "requires": { - "await-event-emitter": "^2.0.2" - } - } + "@reldens/utils": "^0.19.0" } }, "@reldens/storage": { diff --git a/package.json b/package.json index 17a53d718..e52d06a22 100644 --- a/package.json +++ b/package.json @@ -78,9 +78,9 @@ "@parcel/resolver-default": "2.8.0", "@parcel/reporter-dev-server": "2.8.0", "@parcel/core": "2.8.0", - "@reldens/items-system": "^0.17.0", - "@reldens/modifiers": "^0.18.0", - "@reldens/skills": "^0.17.0", + "@reldens/items-system": "^0.18.0", + "@reldens/modifiers": "^0.19.0", + "@reldens/skills": "^0.18.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.19.0", "adminjs": "5.7.3", From 1bf7d0525b9d10e47d19bf241311cc5340d724da Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 29 Mar 2023 20:10:04 +0200 Subject: [PATCH 31/82] - Reldens - v4.0.0 - Clan UI. --- lib/teams/client/clan-message-handler.js | 214 +++++++++++++++++- lib/teams/client/plugin.js | 2 +- lib/teams/client/team-message-handler.js | 26 ++- lib/teams/client/team-target-box-enricher.js | 2 +- lib/teams/constants.js | 6 +- lib/teams/server/clan-updates-handler.js | 2 +- .../server/message-actions/clan-create.js | 47 ++-- .../server/message-actions/team-leave.js | 13 +- lib/teams/server/team-message-actions.js | 12 +- migrations/production/beta.26-sql-update.sql | 4 + package-lock.json | 14 +- package.json | 2 +- .../teams/templates/team-player-data.html | 2 +- .../features/teams/templates/team-remove.html | 2 +- theme/default/css/chat.scss | 4 + theme/default/css/teams.scss | 9 +- 16 files changed, 301 insertions(+), 60 deletions(-) diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 5fdb8f466..ede4cf12e 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -4,17 +4,42 @@ * */ -const { TeamMessageHandler } = require('./team-message-handler'); const { UserInterface } = require('../../game/client/user-interface'); const { TeamsConst } = require('../constants'); -const { sc, Logger} = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); -class ClanMessageHandler extends TeamMessageHandler +class ClanMessageHandler { constructor(props) { - super(props); + this.roomEvents = sc.get(props, 'roomEvents', false); + this.message = sc.get(props, 'message', false); + this.gameManager = this.roomEvents?.gameManager; + this.gameDom = this.gameManager?.gameDom; + this.uiScene = this.gameManager?.gameEngine?.uiScene; + } + + validate() + { + if(!this.roomEvents){ + Logger.notice('Missing RoomEvents on TeamMessageHandler.'); + return false; + } + if(!this.message){ + Logger.notice('Missing message on TeamMessageHandler.'); + return false; + } + if(!this.gameManager){ + Logger.notice('Missing GameManager on TeamMessageHandler.'); + return false; + } + if(!this.uiScene){ + Logger.notice('Missing UI Scene on TeamMessageHandler.'); + return false; + } + return this.gameManager.getCurrentPlayer().playerId; + } initializeClanUi() @@ -29,7 +54,7 @@ class ClanMessageHandler extends TeamMessageHandler Logger.error('Missing container: "#box-clan .box-content".'); return false; } - let uiBox = this.uiScene.elementsUi['clan']; + let uiBox = this.uiScene.elementsUi[TeamsConst.CLAN_KEY]; if(!uiBox){ Logger.error('Clan UI box not found.', {clanUi, container, uiBox}); return false; @@ -41,22 +66,77 @@ class ClanMessageHandler extends TeamMessageHandler showNewClan() { - + let clanUi = this.createClanUi(); + let title = this.gameManager.config.getWithoutLogs( + 'client/clan/labels/clanTitle', + TeamsConst.LABELS.CLAN.CLAN_TITLE + ).replace('%clanName', this.message.clanName) + .replace('%leaderName', this.gameManager.getCurrentPlayer().playerName); + let container = this.gameManager.gameDom.getElement('.clan-dialog-box .box-content'); + if(!container){ + Logger.error('Missing container: ".clan-dialog-box .box-content".'); + return false; + } + let uiBox = this.uiScene.elementsUi[TeamsConst.CLAN_KEY]; + if(!uiBox){ + Logger.error('Clan UI box not found.', {clanUi, container, uiBox}); + return false; + } + this.roomEvents.uiSetTitle(uiBox, {title}); + this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); + this.updateClanBox({}, container); } showClanRequest() { - + let clanUi = this.createClanUi(); + this.roomEvents.initUi({ + id: TeamsConst.CLAN_KEY, + title: this.gameManager.config.getWithoutLogs( + 'client/clan/labels/requestFromTitle', + TeamsConst.LABELS.CLAN.REQUEST_FROM + ), + content: this.message.from, + options: this.gameManager.config.get('client/ui/options/acceptOrDecline'), + overrideSendOptions: {act: TeamsConst.ACTIONS.CLAN_ACCEPTED, id: this.message.id} + }); + this.gameDom.getElement('#opt-2-clan')?.addEventListener('click', () => { + this.removeClanUi(); + }); } showClanBox() { let clanUi = this.createClanUi(); + let title = this.gameManager.config.getWithoutLogs( + 'client/clan/labels/clanTitle', + TeamsConst.LABELS.CLAN.CLAN_TITLE + ) + .replace('%clanName', this.message.clanName) + .replace('%leaderName', this.message.leaderName); + let container = this.gameManager.gameDom.getElement('#box-clan .box-content'); + if(!container){ + Logger.error('Missing container: "#box-clan .box-content".'); + return false; + } + let uiBox = this.uiScene.elementsUi[TeamsConst.CLAN_KEY]; + this.roomEvents.uiSetTitle(uiBox, {title}); + this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); + let players = sc.get(this.message, 'players', false); + this.uiScene.currentTeam = players; + this.updateClanBox(players, container); } removeClanUi() { - + let uiElement = this.gameManager.getUiElement(TeamsConst.CLAN_KEY); + if(!uiElement){ + Logger.error('Clan UI Element not found for remove.'); + return false; + } + uiElement.removeElement(); + delete this.uiScene.userInterfaces[TeamsConst.CLAN_KEY]; + delete this.uiScene.elementsUi[TeamsConst.CLAN_KEY]; } createClanUi() @@ -128,6 +208,124 @@ class ClanMessageHandler extends TeamMessageHandler }); } + updateClanBox(players, container) + { + if(!players){ + Logger.error('Players not defined for clan box update.'); + return; + } + let teamMembers = ''; + for(let i of Object.keys(players)){ + teamMembers += this.createClanMemberBox(players[i]); + } + container.innerHTML = this.createClanContainer(teamMembers); + this.activateClanTargetActions(players); + this.activateClanLeaveButtonAction(); + } + + createClanContainer(teamMembers) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let templateContent = this.uiScene.cache.html.get('clanContainer'); + if(!templateContent){ + Logger.error('Missing template "clanContainer".'); + return ''; + } + let isPlayerOwner = this.gameManager.getCurrentPlayer().playerId === this.message.id; + let leaveActionLabel = isPlayerOwner + ? this.gameManager.config.getWithoutLogs('client/clan/labels/disbandLabel', TeamsConst.LABELS.CLAN.DISBAND) + : this.gameManager.config.getWithoutLogs('client/clan/labels/leaveLabel', TeamsConst.LABELS.CLAN.LEAVE); + let templateParams = { + clanId: this.message.id, + playerId: this.gameManager.getCurrentPlayer().playerId, + leaveActionLabel: leaveActionLabel, + teamMembers + }; + return this.gameManager.gameEngine.parseTemplate(templateContent, templateParams); + } + + activateClanLeaveButtonAction() + { + let leaveButton = this.gameManager.gameDom.getElement('.leave-'+this.message.id); + leaveButton?.addEventListener('click', () => { + this.gameManager.activeRoomEvents.room.send('*', { + act: TeamsConst.ACTIONS.TEAM_LEAVE, + id: this.message.id + }); + }); + } + + createClanMemberBox(playerData) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let templateContent = this.uiScene.cache.html.get('clanPlayerData'); + if(!templateContent){ + Logger.error('Missing template "clanPlayerData".'); + return ''; + } + let isPlayerOwner = playerData.id === this.message.id; + return this.gameManager.gameEngine.parseTemplate(templateContent, { + playerId: playerData.id, + playerName: playerData.name, + playerProperties: this.createSharedPropertiesContent(playerData.sharedProperties), + playerRemove: isPlayerOwner ? this.createDismissPlayerButton(playerData) : '' + }); + } + + createDismissPlayerButton(playerData) + { + let templateContent = this.uiScene.cache.html.get('clanRemove'); + if(!templateContent){ + Logger.error('Missing template "clanRemove".'); + return ''; + } + return this.gameManager.gameEngine.parseTemplate(templateContent, {playerId: playerData.id}); + } + + createSharedPropertiesContent(playerSharedProperties) + { + let templateContent = this.uiScene.cache.html.get('teamsSharedProperty'); + if(!templateContent){ + Logger.error('Missing template "teamsSharedProperty".'); + return ''; + } + let sharedPropertiesContent = ''; + // @TODO - BETA - Move the template load from cache as part of the engine driver. + for(let i of Object.keys(playerSharedProperties)) { + templateContent = this.uiScene.cache.html.get('teamsSharedProperty'); + let propertyData = playerSharedProperties[i]; + let propertyMaxValue = sc.get(propertyData, 'max', ''); + if('' !== propertyMaxValue){ + propertyMaxValue = this.gameManager.config.getWithoutLogs( + 'client/clan/labels/propertyMaxValue', + TeamsConst.LABELS.CLAN.PROPERTY_MAX_VALUE + ).replace('%propertyMaxValue', propertyMaxValue); + } + sharedPropertiesContent += this.gameManager.gameEngine.parseTemplate(templateContent, { + key: i, + label: propertyData.label, + value: propertyData.value, + max: propertyMaxValue + }); + } + return sharedPropertiesContent; + } + + activateClanTargetActions(playersData) + { + for(let i of Object.keys(playersData)){ + let playerData = playersData[i]; + let selectorPlayerName = '.clan-player-'+i+' .player-name'; + let selectorPlayerProperties = '.clan-player-'+i+' .properties-list-container'; + this.gameDom.getElement(selectorPlayerName).addEventListener('click', () => { + this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); + }); + this.gameDom.getElement(selectorPlayerProperties).addEventListener('click', () => { + this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); + }); + } + } + } module.exports.ClanMessageHandler = ClanMessageHandler; diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index e87caf42b..f26e8624a 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -28,7 +28,7 @@ class TeamsPlugin extends PluginInterface } this.teamMessageListener = new TeamMessageListener(); this.clanMessageListener = new ClanMessageListener(); - this.events.on('reldens.createEngineScene', (uiScene) => { + this.events.on('reldens.createEngineSceneDone', (uiScene) => { let roomEvents = uiScene?.gameManager?.activeRoomEvents || this.gameManager?.activeRoomEvents; if(!roomEvents){ Logger.critical('RoomEvents undefined for process Team messages queue on TeamsPlugin.'); diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index b33f30129..222da7b6f 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -47,7 +47,7 @@ class TeamMessageHandler this.roomEvents.initUi({ id: this.teamUiKey(), title: this.gameManager.config.getWithoutLogs( - 'client/teams/labels/requestFromTitle', + 'client/team/labels/requestFromTitle', TeamsConst.LABELS.TEAM.REQUEST_FROM ), content: this.message.from, @@ -128,7 +128,7 @@ class TeamMessageHandler teamMembers += this.createTeamMemberBox(players[i]); } container.innerHTML = this.createTeamContainer(teamMembers); - this.activateTeamTargetActions(players); + this.activateTeamPlayerActions(players); this.activateTeamLeaveButtonAction(); } @@ -143,8 +143,8 @@ class TeamMessageHandler let playerId = this.gameManager.getCurrentPlayer().playerId; let isPlayerOwner = playerId === this.message.id; let leaveActionLabel = isPlayerOwner - ? this.gameManager.config.getWithoutLogs('client/team/titles/disbandLabel', TeamsConst.LABELS.TEAM.DISBAND) - : this.gameManager.config.getWithoutLogs('client/team/titles/leaveLabel', TeamsConst.LABELS.TEAM.LEAVE); + ? this.gameManager.config.getWithoutLogs('client/team/labels/disbandLabel', TeamsConst.LABELS.TEAM.DISBAND) + : this.gameManager.config.getWithoutLogs('client/team/labels/leaveLabel', TeamsConst.LABELS.TEAM.LEAVE); let templateParams = { teamId: this.message.id, playerId, @@ -201,7 +201,7 @@ class TeamMessageHandler } let sharedPropertiesContent = ''; // @TODO - BETA - Move the template load from cache as part of the engine driver. - for(let i of Object.keys(playerSharedProperties)) { + for(let i of Object.keys(playerSharedProperties)){ templateContent = this.uiScene.cache.html.get('teamsSharedProperty'); let propertyData = playerSharedProperties[i]; let propertyMaxValue = sc.get(propertyData, 'max', ''); @@ -221,18 +221,26 @@ class TeamMessageHandler return sharedPropertiesContent; } - activateTeamTargetActions(playersData) + activateTeamPlayerActions(playersData) { for(let i of Object.keys(playersData)){ let playerData = playersData[i]; let selectorPlayerName = '.team-player-'+i+' .player-name'; - let selectorPlayerProperties = '.team-player-'+i+' .properties-list-container'; - this.gameDom.getElement(selectorPlayerName).addEventListener('click', () => { + this.gameDom.getElement(selectorPlayerName)?.addEventListener('click', () => { this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); - this.gameDom.getElement(selectorPlayerProperties).addEventListener('click', () => { + let selectorPlayerProperties = '.team-player-'+i+' .properties-list-container'; + this.gameDom.getElement(selectorPlayerProperties)?.addEventListener('click', () => { this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); + let selectorPlayerRemove = '.team-player-'+i+' .team-remove-button'; + this.gameDom.getElement(selectorPlayerRemove)?.addEventListener('click', () => { + this.gameManager.activeRoomEvents.room.send('*', { + act: TeamsConst.ACTIONS.TEAM_REMOVE, + id: this.message.id, + remove: playerData.id + }); + }); } } diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/team-target-box-enricher.js index 09991686f..3da273e42 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -59,7 +59,7 @@ class TeamTargetBoxEnricher playerName: targetName, playerId: target.id, inviteLabel: gameManager.config.getWithoutLogs( - type+'/titles/inviteLabel', + type+'/labels/inviteLabel', TeamsConst.LABELS[type.toUpperCase()].INVITE_BUTTON_LABEL ) } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index 087468be2..ba4912a7c 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -18,7 +18,8 @@ module.exports.TeamsConst = { SUCCESS: 1, NAME_EXISTS: 2, LEVEL_ISSUE: 3, - CREATE_ERROR: 4 + CREATE_ERROR: 4, + CREATE_OWNER_ERROR: 5 }, ACTIONS: { // @TODO - BETA - Standardize generic actions and use dots to split, like UPDATE = '.up', REMOVE = '.rm', etc. @@ -36,6 +37,7 @@ module.exports.TeamsConst = { CLAN_UPDATE: clanPref+'upd', CLAN_LEFT: clanPref+'lef', CLAN_REMOVE: clanPref+'rem', + CLAN_REMOVED: clanPref+'remd', CLAN_NAME: clanPref+'nam' }, LABELS: { @@ -51,7 +53,7 @@ module.exports.TeamsConst = { CREATE_CLAN_TITLE: 'Clan - Creation', INVITE_BUTTON_LABEL: 'Clan - Invite', REQUEST_FROM: 'Accept clan request from:', - LEADER_NAME_TITLE: 'Clan leader: %leaderName', + CLAN_TITLE: 'Clan: %clanName - Leader: %leaderName', NAME_PLACEHOLDER: 'Choose a clan name...', CREATE: 'Create', DISBAND: 'Disband Clan', diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 6a66b1d36..070c46e94 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -30,7 +30,7 @@ class ClanUpdatesHandler listener: TeamsConst.CLAN_KEY, players: otherPlayersData, leaderName: clan.owner.playerName, - clan: clan.toArray() + clanName: clan.name }; clan.clients[i].send('*', sendUpdate); } diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index db8aaf73b..ac1bfe3bb 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -4,12 +4,12 @@ * */ -const { ClanUpdatesHandler } = require('../clan-updates-handler'); -const { Clan } = require('../clan'); const { TeamsConst } = require('../../constants'); +const { Logger } = require('@reldens/utils'); class ClanCreate { + static async execute(client, data, room, playerSchema, teamsPlugin) { let characterLimit = room.config.getWithoutLogs('server/clan/settings/nameLimit', TeamsConst.NAME_LIMIT); @@ -17,7 +17,8 @@ class ClanCreate let repository = teamsPlugin.dataServer.getEntity('clan'); let exists = await repository.loadOneBy('name', clanName); if(exists){ - client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.NAME_EXISTS}); + Logger.info('Clan already exists with name "'+clanName+'".'); + this.clanCreateSend(client, TeamsConst.VALIDATION.NAME_EXISTS, clanName); return; } let levelsRepository = teamsPlugin.dataServer.getEntity('clanLevels'); @@ -25,7 +26,8 @@ class ClanCreate levelsRepository.sortBy = 'key'; let firstLevel = await levelsRepository.loadOne({}); if(!firstLevel){ - client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.LEVEL_ISSUE}); + Logger.info('Clan creation invalid level "'+clanName+'".'); + this.clanCreateSend(client, TeamsConst.VALIDATION.LEVEL_ISSUE, clanName); return; } levelsRepository.limit = 0; @@ -37,26 +39,37 @@ class ClanCreate level: firstLevel.key }); if(!createdClan){ - client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.CREATE_ERROR}); + Logger.info('Clan creation error "'+clanName+'".', createdClan); + this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_ERROR, clanName); return; } - client.send('*', {act: TeamsConst.ACTIONS.CLAN_CREATE, result: TeamsConst.VALIDATION.SUCCESS}); - /* let ownerMember = await teamsPlugin.dataServer.getEntity('clanMembers').create({ clan_id: createdClan.id, player_id: playerSchema.player_id }); - ownerMember.parent_player = await teamsPlugin.dataServer.getEntity('players').loadById(playerSchema.player_id); - createdClan.members = [ownerMember]; - let newClan = Clan.fromModel({ - clanModel: createdClan, - owner: playerSchema, - ownerClient: client, - sharedProperties: room.config.get('client/ui/teams/sharedProperties') - }); - ClanUpdatesHandler.updateClanPlayers(newClan); - */ + if(!ownerMember){ + Logger.info('Clan owner creation error "'+clanName+'".', createdClan, ownerMember); + this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_OWNER_ERROR, clanName); + return; + } + this.clanCreateSend(client, TeamsConst.VALIDATION.SUCCESS, clanName, playerSchema.player_id); } + + static clanCreateSend(client, result, clanName, ownerId) + { + // @TODO - BETA - Include additional event before send. + let sendData = { + act: TeamsConst.ACTIONS.CLAN_CREATE, + listener: TeamsConst.CLAN_KEY, + clanName, + result + }; + if(ownerId){ + sendData.id = ownerId; + } + client.send('*', sendData); + } + } module.exports.ClanCreate = ClanCreate; diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 4d18c58a1..0153451ea 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -6,7 +6,7 @@ const { TeamUpdatesHandler } = require('../team-updates-handler'); const { TeamsConst } = require('../../constants'); -const { Logger } = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); class TeamLeave { @@ -14,18 +14,23 @@ class TeamLeave static async execute(client, data, room, playerSchema, teamsPlugin) { await teamsPlugin.events.emit('reldens.teamLeave', {client, data, room, playerSchema, teamsPlugin}); + if(TeamsConst.ACTIONS.TEAM_REMOVE === data.act && data.id !== playerSchema.sessionId){ + Logger.error('Team remove failed, player "'+playerSchema.playerName+'" not allowed.'); + return false; + } let teamId = playerSchema.currentTeam; if(!teamId){ return false; } let currentTeam = teamsPlugin.teams[teamId]; if(!currentTeam){ - Logger.error('Player "'+playerSchema.player_id+'"current team "'+teamId+'"not found.'); + Logger.error('Player "'+playerSchema.player_id+'" current team "'+teamId+'" not found.'); playerSchema.currentTeam = false; return false; } let playerIds = Object.keys(currentTeam.players); - let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [playerSchema.id]; + let singleRemoveId = sc.get(data, 'remove', playerSchema.id); + let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ let sendUpdate = { act: TeamsConst.ACTIONS.TEAM_LEFT, @@ -44,7 +49,7 @@ class TeamLeave currentTeam.clients[playerId].send('*', sendUpdate); currentTeam.leave(currentTeam.players[playerId]); } - if(0 === Object.keys(currentTeam.players).length){ + if(1 >= Object.keys(currentTeam.players).length){ let event = {client, data, room, playerSchema, teamsPlugin, continueDisband: true}; await teamsPlugin.events.emit('reldens.beforeTeamDisband', event); if(!event.continueDisband){ diff --git a/lib/teams/server/team-message-actions.js b/lib/teams/server/team-message-actions.js index b79bd0e47..e8f604e41 100644 --- a/lib/teams/server/team-message-actions.js +++ b/lib/teams/server/team-message-actions.js @@ -27,16 +27,16 @@ class TeamMessageActions return false; } if(TeamsConst.ACTIONS.TEAM_INVITE === data.act){ - await TryTeamStart.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await TryTeamStart.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.TEAM_ACCEPTED === data.act && '1' === data.value){ - await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); + } + if(TeamsConst.ACTIONS.TEAM_REMOVE === data.act){ + return await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); } } diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index bba0e0682..249e97cb3 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -47,6 +47,10 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/requestFromTitle', 'Clan request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/requestFromTitle', 'Clan request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/clanTitle', 'Clan: %clanName - Leader: %leaderName', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/enabled', '1', @boolean_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/responsiveX', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/responsiveY', '0', @float_id); diff --git a/package-lock.json b/package-lock.json index c354ae935..7eebacc24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "@parcel/transformer-svg": "2.8.0", "@parcel/transformer-webmanifest": "2.8.0", "@parcel/transformer-worklet": "2.8.0", - "@reldens/items-system": "^0.18.0", + "@reldens/items-system": "^0.18.1", "@reldens/modifiers": "^0.19.0", "@reldens/skills": "^0.18.0", "@reldens/storage": "^0.13.0", @@ -5462,9 +5462,9 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@reldens/items-system": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.0.tgz", - "integrity": "sha512-P7ruOj7JUwi8vSeg211zLMGXnDHto4wVSjJLH50cRuxOlrJq1eiuwUTkx66wyZopZSQ7mD3u9mbpaAYVoSoWBw==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.1.tgz", + "integrity": "sha512-gTKCb/Y2THC+Gd0V1IO8HiYHAk5oOvJMcIXCe/NX8TVrFs1Rr2mkWvMa30fBB37cei39zXGaiBdza1sSam3zWQ==", "dependencies": { "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", @@ -16406,9 +16406,9 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@reldens/items-system": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.0.tgz", - "integrity": "sha512-P7ruOj7JUwi8vSeg211zLMGXnDHto4wVSjJLH50cRuxOlrJq1eiuwUTkx66wyZopZSQ7mD3u9mbpaAYVoSoWBw==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/items-system/-/items-system-0.18.1.tgz", + "integrity": "sha512-gTKCb/Y2THC+Gd0V1IO8HiYHAk5oOvJMcIXCe/NX8TVrFs1Rr2mkWvMa30fBB37cei39zXGaiBdza1sSam3zWQ==", "requires": { "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", diff --git a/package.json b/package.json index e52d06a22..046a0826e 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@parcel/resolver-default": "2.8.0", "@parcel/reporter-dev-server": "2.8.0", "@parcel/core": "2.8.0", - "@reldens/items-system": "^0.18.0", + "@reldens/items-system": "^0.18.1", "@reldens/modifiers": "^0.19.0", "@reldens/skills": "^0.18.0", "@reldens/storage": "^0.13.0", diff --git a/theme/default/assets/features/teams/templates/team-player-data.html b/theme/default/assets/features/teams/templates/team-player-data.html index b3a60e778..304f77003 100644 --- a/theme/default/assets/features/teams/templates/team-player-data.html +++ b/theme/default/assets/features/teams/templates/team-player-data.html @@ -2,7 +2,7 @@
{{playerName}}
- {{&teamRemove}} + {{&playerRemove}}
{{&playerProperties}}
diff --git a/theme/default/assets/features/teams/templates/team-remove.html b/theme/default/assets/features/teams/templates/team-remove.html index 588361e0d..63ee3fd2d 100644 --- a/theme/default/assets/features/teams/templates/team-remove.html +++ b/theme/default/assets/features/teams/templates/team-remove.html @@ -1,3 +1,3 @@
- remove + remove
diff --git a/theme/default/css/chat.scss b/theme/default/css/chat.scss index 60423f954..dafc4af87 100644 --- a/theme/default/css/chat.scss +++ b/theme/default/css/chat.scss @@ -15,6 +15,10 @@ background: #000000; opacity: 0.8; color: $cWhite; + /* help for chrome issue with scale on phaser dom elements + will-change: transform; + transform: scale(1.011); + */ } #chat-messages { diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index beedb02c3..fc9ee31fc 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -12,9 +12,16 @@ } .clan-dialog-box { + float: left; + position: relative; min-width: 160px; + min-height: 100px; top: 250px; right: 360px; + /* help for chrome issue with scale on phaser dom elements + will-change: transform; + transform: scale(1.009); + */ .clan-name-input { outline: none; @@ -83,7 +90,7 @@ display: block; width: 10%; - #team-remove { + .team-remove-button { position: absolute; top: 0; right: 0; From 90b20bce3aa36a61362723761f4852f39c010f77 Mon Sep 17 00:00:00 2001 From: Lucio Vicentini Date: Wed, 25 Jan 2023 14:25:09 +0100 Subject: [PATCH 32/82] - Reldens - v4.0.0-beta.26 - Full rewards and drops features by Lucio Vicentini. --- lib/chat/constants.js | 5 +- lib/chat/server/plugin.js | 10 +- lib/features/client/config-client.js | 4 +- lib/features/server/config-server.js | 4 +- lib/game/constants.js | 4 + lib/objects/client/animation-engine.js | 2 + lib/objects/client/plugin.js | 41 +++-- lib/objects/constants.js | 1 + lib/objects/server/manager.js | 103 +++++++---- lib/objects/server/object/type/drop-object.js | 26 +++ lib/rewards/client/plugin.js | 38 ++++ .../client/rewards-message-listener.js | 132 +++++++++++++ lib/rewards/constants.js | 18 ++ lib/rewards/server/entities-config.js | 22 +++ lib/rewards/server/entities-translations.js | 13 ++ ...objects-items-rewards-animations-entity.js | 55 ++++++ lib/rewards/server/entities/rewards-entity.js | 66 +++++++ .../entities/rewards-modifiers-entity.js | 70 +++++++ lib/rewards/server/message-actions.js | 75 ++++++++ .../objects-items-rewards-animations-model.js | 34 ++++ .../registered-models-objection-js.js | 23 +++ .../models/objection-js/rewards-model.js | 61 ++++++ .../objection-js/rewards-modifiers-model.js | 34 ++++ .../objects_items_rewards_animations.js | 38 ++++ lib/rewards/server/models/reward.js | 163 +++++++++++++++++ lib/rewards/server/models/rewards-mapper.js | 19 ++ lib/rewards/server/plugin.js | 53 ++++++ .../server/subscribers/object-subscriber.js | 24 +++ .../server/subscribers/rewards-subscriber.js | 173 ++++++++++++++++++ .../subscribers/sender-drop-subscriber.js | 27 +++ .../subscribers/world-drop-subscriber.js | 145 +++++++++++++++ lib/rooms/server/scene.js | 30 +++ lib/world/server/p2world.js | 5 +- migrations/production/beta.26-sql-update.sql | 54 +++++- .../assets/custom/sprites/branch-sprite.png | Bin 0 -> 2233 bytes theme/plugins/objects/server/enemy1-object.js | 10 +- theme/plugins/objects/server/enemy2-object.js | 2 + theme/plugins/server-plugin.js | 3 + 38 files changed, 1511 insertions(+), 76 deletions(-) create mode 100644 lib/objects/server/object/type/drop-object.js create mode 100644 lib/rewards/client/plugin.js create mode 100644 lib/rewards/client/rewards-message-listener.js create mode 100644 lib/rewards/constants.js create mode 100644 lib/rewards/server/entities-config.js create mode 100644 lib/rewards/server/entities-translations.js create mode 100644 lib/rewards/server/entities/objects-items-rewards-animations-entity.js create mode 100644 lib/rewards/server/entities/rewards-entity.js create mode 100644 lib/rewards/server/entities/rewards-modifiers-entity.js create mode 100644 lib/rewards/server/message-actions.js create mode 100644 lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js create mode 100644 lib/rewards/server/models/objection-js/registered-models-objection-js.js create mode 100644 lib/rewards/server/models/objection-js/rewards-model.js create mode 100644 lib/rewards/server/models/objection-js/rewards-modifiers-model.js create mode 100644 lib/rewards/server/models/objects_items_rewards_animations.js create mode 100644 lib/rewards/server/models/reward.js create mode 100644 lib/rewards/server/models/rewards-mapper.js create mode 100644 lib/rewards/server/plugin.js create mode 100644 lib/rewards/server/subscribers/object-subscriber.js create mode 100644 lib/rewards/server/subscribers/rewards-subscriber.js create mode 100644 lib/rewards/server/subscribers/sender-drop-subscriber.js create mode 100644 lib/rewards/server/subscribers/world-drop-subscriber.js create mode 100644 theme/default/assets/custom/sprites/branch-sprite.png diff --git a/lib/chat/constants.js b/lib/chat/constants.js index 764524d17..cfef359fe 100644 --- a/lib/chat/constants.js +++ b/lib/chat/constants.js @@ -26,6 +26,7 @@ module.exports.ChatConst = { CHAT_SYSTEM: 's', CHAT_PRIVATE: 'p', CHAT_DAMAGE: 'd', + CHAT_REWARD: 'r', CHAT_SKILL: 'ss', // @TODO - BETA - Refactor to CHAT_TYPE: {} CHAT_TYPE_NORMAL: 'ctn', @@ -36,6 +37,7 @@ module.exports.ChatConst = { CHAT_TYPE_SYSTEM_ERROR: 'ctse', CHAT_TYPE_SYSTEM_BATTLE: 'ctse', CHAT_TYPE_SYSTEM_BATTLE_MODIFIERS: 'ctsem', + CHAT_TYPE_REWARD: 'ctr', // @TODO - BETA - Refactor to COLORS. colors: { ctn: '#ffffff', @@ -50,6 +52,7 @@ module.exports.ChatConst = { MESSAGES: { ON: ' on ', HIT_ON: ' hit on ', - DODGED: ' dodged ' + DODGED: ' dodged ', + REWARD: 'You obtained %dropQuantity %itemLabel' } }; diff --git a/lib/chat/server/plugin.js b/lib/chat/server/plugin.js index 46da566cf..75bdb3ddc 100644 --- a/lib/chat/server/plugin.js +++ b/lib/chat/server/plugin.js @@ -18,27 +18,27 @@ class ChatPlugin extends PluginInterface setup(props) { this.events = sc.get(props, 'events', false); - if(!this.events){ + if (!this.events) { Logger.error('EventsManager undefined in ChatPlugin.'); } this.dataServer = sc.get(props, 'dataServer', false); - if(!this.dataServer){ + if (!this.dataServer) { Logger.error('DataServer undefined in ChatPlugin.'); } - this.chatManager = new ChatManager({dataServer: this.dataServer}); + this.chatManager = new ChatManager({ dataServer: this.dataServer }); // rooms is the list of the current feature rooms names that later will be sent to the client and used to join. this.rooms = ['chat']; // then we can use the event manager to append the feature in every action required: this.events.on('reldens.roomsDefinition', (roomsList) => { // here we are adding the chat room to be defined in the game server: - roomsList.push({roomName: 'chat', room: RoomChat}); + roomsList.push({ roomName: 'chat', room: RoomChat }); }); this.events.on('reldens.serverConfigFeaturesReady', (props) => { this.chatConfig = props.configProcessor.get('client/ui/chat'); }); // when the client sent a message to any room it will be checked by all the global messages defined: this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { - roomMessageActions.chat = new ChatMessageActions({dataServer: this.dataServer}); + roomMessageActions.chat = new ChatMessageActions({ dataServer: this.dataServer }); }); this.events.on('reldens.actionsPrepareEventsListeners', async (actionsPack, classPath) => { PlayerSkills.listenEvents(classPath, this.chatConfig, this.chatManager); diff --git a/lib/features/client/config-client.js b/lib/features/client/config-client.js index 827f21d8a..64427c72e 100644 --- a/lib/features/client/config-client.js +++ b/lib/features/client/config-client.js @@ -17,6 +17,7 @@ const { AudioPlugin } = require('../../audio/client/plugin'); const { RoomsPlugin } = require('../../rooms/client/plugin'); const { PredictionPlugin } = require('../../prediction/client/plugin'); const { TeamsPlugin } = require('../../teams/client/plugin'); +const { RewardsPlugin } = require('../../rewards/client/plugin'); module.exports.ClientCoreFeatures = { chat: ChatPlugin, @@ -27,5 +28,6 @@ module.exports.ClientCoreFeatures = { audio: AudioPlugin, rooms: RoomsPlugin, prediction: PredictionPlugin, - teams: TeamsPlugin + teams: TeamsPlugin, + rewards: RewardsPlugin }; diff --git a/lib/features/server/config-server.js b/lib/features/server/config-server.js index f3bc3676a..410d8955f 100644 --- a/lib/features/server/config-server.js +++ b/lib/features/server/config-server.js @@ -18,6 +18,7 @@ const { AudioPlugin } = require('../../audio/server/plugin'); const { RoomsPlugin } = require('../../rooms/server/plugin'); const { AdminPlugin } = require('../../admin/server/plugin'); const { TeamsPlugin } = require('../../teams/server/plugin'); +const { RewardsPlugin } = require('../../rewards/server/plugin'); module.exports.ServerCoreFeatures = { chat: ChatPlugin, @@ -29,5 +30,6 @@ module.exports.ServerCoreFeatures = { audio: AudioPlugin, rooms: RoomsPlugin, admin: AdminPlugin, - teams: TeamsPlugin + teams: TeamsPlugin, + rewards: RewardsPlugin }; diff --git a/lib/game/constants.js b/lib/game/constants.js index 76b607aeb..30ec64282 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -56,5 +56,9 @@ module.exports.GameConst = { LABELS: { YES: 'Yes', NO: 'No' + }, + GRAPHICS: { + FRAME_WIDTH: 32, + FRAME_HEIGHT: 32 } }; diff --git a/lib/objects/client/animation-engine.js b/lib/objects/client/animation-engine.js index e6fc9a298..3cf080a26 100644 --- a/lib/objects/client/animation-engine.js +++ b/lib/objects/client/animation-engine.js @@ -121,6 +121,7 @@ class AnimationEngine this.currentPreloader.objectsAnimations[this.key] = this.currentAnimation; let spriteX = this.positionFix ? this.animPos.x : this.x; let spriteY = this.positionFix ? this.animPos.y : this.y; + // This is where the animation is actually been created and stored. this.sceneSprite = currentScene.physics.add.sprite(spriteX, spriteY, this.asset_key); if(this.autoStart){ this.sceneSprite.anims.play(this.key, true); @@ -134,6 +135,7 @@ class AnimationEngine this.sceneSprite.setDepth(this.y + this.sceneSprite.body.height); currentScene.objectsAnimations[this.key] = this; this.gameManager.events.emitSync('reldens.createAnimationAfter', {animationEngine: this}); + return this.sceneSprite; } automaticDestroyOnComplete() diff --git a/lib/objects/client/plugin.js b/lib/objects/client/plugin.js index 8af86baf4..1619886ed 100644 --- a/lib/objects/client/plugin.js +++ b/lib/objects/client/plugin.js @@ -265,27 +265,32 @@ class ObjectsPlugin extends PluginInterface await this.events.emit('reldens.createDynamicAnimationsBefore', this, sceneDynamic); for(let i of Object.keys(currentScene.objectsAnimationsData)){ let animProps = currentScene.objectsAnimationsData[i]; - if(!animProps.key){ - Logger.error(['Animation key not specified. Skipping registry:', animProps]); - continue; - } - animProps.frameRate = sceneDynamic.configuredFrameRate; - await this.events.emit('reldens.createDynamicAnimation_'+animProps.key, this, animProps); - // check for custom class: - let classDefinition = sceneDynamic.gameManager.config.getWithoutLogs( - 'client/customClasses/objects/'+animProps.key - ); - if(!classDefinition){ - // or set default: - classDefinition = AnimationEngine; - } - // create the animation object instance: - let animation = new classDefinition(sceneDynamic.gameManager, animProps, sceneDynamic); - // @NOTE: this will populate the objectsAnimations property in the current scene, see scene-dynamic. - animation.createAnimation(); + await this.createAnimationFromAnimData(animProps, sceneDynamic); } } + async createAnimationFromAnimData(animProps, sceneDynamic) + { + if(!animProps.key){ + Logger.error(['Animation key not specified. Skipping registry:', animProps]); + return false; + } + animProps.frameRate = sceneDynamic.configuredFrameRate; + await this.events.emit('reldens.createDynamicAnimation_'+animProps.key, this, animProps); + // check for custom class: + let classDefinition = sceneDynamic.gameManager.config.getWithoutLogs( + 'client/customClasses/objects/'+animProps.key + ); + if(!classDefinition){ + // or set default: + classDefinition = AnimationEngine; + } + // create the animationEngine object instance: + let animationEngine = new classDefinition(sceneDynamic.gameManager, animProps, sceneDynamic); + // @NOTE: this will populate the objectsAnimations property in the current scene, see scene-dynamic. + animationEngine.createAnimation(); + return animationEngine; + } } module.exports.ObjectsPlugin = ObjectsPlugin; diff --git a/lib/objects/constants.js b/lib/objects/constants.js index 1c8647776..7f3caaf78 100644 --- a/lib/objects/constants.js +++ b/lib/objects/constants.js @@ -15,6 +15,7 @@ module.exports.ObjectsConst = { TYPE_NPC: 'npc', TYPE_ENEMY: 'enemy', TYPE_TRADER: 'trader', + TYPE_DROP: 'drop', DYNAMIC_ANIMATION: 'dyn', DEFAULTS: { BASE_OBJECT: { diff --git a/lib/objects/server/manager.js b/lib/objects/server/manager.js index cb1b2596e..08fddc070 100644 --- a/lib/objects/server/manager.js +++ b/lib/objects/server/manager.js @@ -5,6 +5,7 @@ */ const { Logger, sc } = require('@reldens/utils'); +const { DropObject } = require('./object/type/drop-object'); class ObjectsManager { @@ -52,46 +53,50 @@ class ObjectsManager } this.roomObjects = {}; // @NOTE: allow null index for multiple objects of the same type. - for(let objectData of this.roomObjectsData){ - // @NOTE: these classes are coming from the theme/plugins/objects/server.js file. - let objClass = this.config.get('server/customClasses/objects/'+objectData.object_class_key); - if(!objClass){ - Logger.error([ - 'ObjectManager class not found.', - '- Object ID:', objectData.id, - '- Custom class:', objectData.object_class_key - ]); - continue; + for (let objectData of this.roomObjectsData) { + await this.generateObjectFromObjectData(objectData); + } + } + + async generateObjectFromObjectData(objectData) + { + // @NOTE: these classes are coming from the theme/plugins/objects/server.js file. + let objClass = this.config.get('server/customClasses/objects/' + objectData.object_class_key); + if (!objClass) { + Logger.error([ + 'ObjectManager class not found.', + '- Object ID:', objectData.id, + '- Custom class:', objectData.object_class_key + ]); + return false; + } + let objProps = Object.assign( + { config: this.config, events: this.events, dataServer: this.dataServer }, + objectData + ); + this.prepareInitialStats(objProps); + try { + let objectInstance = new objClass(objProps); + this.attachToAnimations(objectInstance); + if (sc.hasOwn(objectInstance, 'multiple')) { + objectInstance.objProps = objProps; } - let objProps = Object.assign( - {config: this.config, events: this.events, dataServer: this.dataServer}, - objectData - ); - this.prepareInitialStats(objProps); - try { - let objectInstance = new objClass(objProps); - this.attachToAnimations(objectInstance); - if(sc.hasOwn(objectInstance, 'multiple')){ - objectInstance.objProps = objProps; - } - this.enrichWithMultipleAnimationsData(objectData, objectInstance); - this.attachToMessagesListeners(objectInstance, objectData); - this.prepareAssetsPreload(objectData); - await this.runAdditionalSetup(objectInstance, objectData); - this.events.emit('reldens.afterRunAdditionalSetup', { - objectInstance, - objectData, - objectsManager: this - }); - // save object: - this.roomObjects[objectInstance.objectIndex] = objectInstance; - if(!this.roomObjectsByLayer[objectData.layer_name]){ - this.roomObjectsByLayer[objectData.layer_name] = {}; - } - this.roomObjectsByLayer[objectData.layer_name][objectData.id] = objectInstance; - } catch(err) { - Logger.error({'Error while generating object': err, 'Object Data': objectData}); + this.enrichWithMultipleAnimationsData(objectData, objectInstance); + this.attachToMessagesListeners(objectInstance, objectData); + this.prepareAssetsPreload(objectData); + await this.runAdditionalSetup(objectInstance, objectData); + this.events.emit('reldens.afterRunAdditionalSetup', { + objectInstance, + objectData, + objectsManager: this + }); + this.roomObjects[objectInstance.objectIndex] = objectInstance; + if (!this.roomObjectsByLayer[objectData.layer_name]) { + this.roomObjectsByLayer[objectData.layer_name] = {}; } + this.roomObjectsByLayer[objectData.layer_name][objectData.id] = objectInstance; + } catch (err) { + Logger.error({ 'Error while generating object': err, 'Object Data': objectData }); } } @@ -172,6 +177,28 @@ class ObjectsManager return false; } + removeObjectData(rewardObject) + { + this.removeFromPreloadAssetsArray(rewardObject.objects_assets); + delete this.roomObjects[rewardObject.objectIndex]; + delete this.objectsAnimationsData[rewardObject.objectIndex]; + } + + removeFromPreloadAssetsArray(objectAssets) + { + for (let objectAsset of objectAssets) { + this.preloadAssets = this.preloadAssets.filter(obj => obj.object_id !== objectAsset.object_id); + } + } + + hasRewardsDropped() { + for (let roomObject of Object.values(this.roomObjects)) { + if (roomObject instanceof DropObject) { + return true + } + } + return false; + } } module.exports.ObjectsManager = ObjectsManager; diff --git a/lib/objects/server/object/type/drop-object.js b/lib/objects/server/object/type/drop-object.js new file mode 100644 index 000000000..a69ce44fb --- /dev/null +++ b/lib/objects/server/object/type/drop-object.js @@ -0,0 +1,26 @@ +/** + * + * Reldens - DropObject + * + */ + +const { AnimationObject } = require('./animation-object'); +const { RewardsConst } = require('../../../../rewards/constants'); + +class DropObject extends AnimationObject +{ + + constructor(props) + { + super(props); + this.type = RewardsConst.REWARDS_PICK_UP_ACT; + this.eventsPrefix = RewardsConst.DROP_EVENT_PREFIX; + this.listenMessages = true; + this.clientParams.type = RewardsConst.REWARDS_PICK_UP_ACT; + this.clientParams.isInteractive = true; + this.interactionArea = this.config.get('server/rewards/actions/interactionsDistance'); + } + +} + +module.exports.DropObject = DropObject; diff --git a/lib/rewards/client/plugin.js b/lib/rewards/client/plugin.js new file mode 100644 index 000000000..5d9e58edc --- /dev/null +++ b/lib/rewards/client/plugin.js @@ -0,0 +1,38 @@ +const { sc, Logger } = require('@reldens/utils'); +const { PluginInterface } = require('../../features/plugin-interface'); +const { RewardsMessageListener } = require('./rewards-message-listener'); + +class RewardsPlugin extends PluginInterface +{ + + setup(props) + { + super.setup(props); + if (!this.validateProps(props)) { + return false; + } + this.events.on('reldens.joinedRoom', (room, gameManager) => { + RewardsMessageListener.listenMessages(room, gameManager); + }); + } + + validateProps(props) + { + let isValid = true; + this.gameManager = sc.get(props, 'gameManager', false); + if (!this.gameManager) { + Logger.error('Game Manager undefined in RewardsPlugin.'); + isValid = false; + } + this.events = sc.get(props, 'events', false); + if (!this.events) { + Logger.error('EventsManager undefined in RewardsPlugin.'); + isValid = false; + } + return isValid; + } + + +} + +module.exports.RewardsPlugin = RewardsPlugin; \ No newline at end of file diff --git a/lib/rewards/client/rewards-message-listener.js b/lib/rewards/client/rewards-message-listener.js new file mode 100644 index 000000000..5b783c38c --- /dev/null +++ b/lib/rewards/client/rewards-message-listener.js @@ -0,0 +1,132 @@ +const { sc, Logger } = require('@reldens/utils'); +const { RewardsConst } = require('../constants'); +const { GameConst } = require('../../game/constants'); + +class RewardsMessageListener +{ + + static listenMessages(room, gameManager) + { + room.onMessage('*', (message) => { + let rewards = sc.get(message, RewardsConst.REWARDS_CONST, false); + if (rewards) { + this.loadRewards(rewards, gameManager); + } + + if (RewardsConst.REMOVE_DROP === message.act) { + this.removeRewardById(message.id, gameManager); + } + }); + } + + static loadRewards(rewards, gameManager) + { + let currentScene = gameManager.getActiveScene(); + let gameConfig = gameManager.config; + let objectPlugin = gameManager.getFeature('objects'); + let loader = currentScene.load; + if (!this.validateParams({ currentScene, gameConfig, objectPlugin, loader })) { + return false; + } + + for (let [rewardId, reward] of Object.entries(rewards)) { + this.loadSpritesheet(reward, loader, gameConfig); + loader.once('complete', async () => { + await this.createRewardAnimation(objectPlugin, reward, rewardId, currentScene); + }); + } + loader.start(); + return true; + } + + static async createRewardAnimation(objectsPlugin, reward, rewardId, currentScene) + { + return await objectsPlugin.createAnimationFromAnimData({ + type: RewardsConst.REWARDS_PICK_UP_ACT, + enabled: true, + ui: true, + frameStart: reward[RewardsConst.REWARDS_PARAMS]['start'], + frameEnd: reward[RewardsConst.REWARDS_PARAMS]['end'], + repeat: reward[RewardsConst.REWARDS_PARAMS]['repeat'], + autoStart: true, + key: rewardId, + id: rewardId, + targetName: '', + layerName: rewardId, + isInteractive: true, + asset_key: reward[RewardsConst.REWARDS_ASSET_KEY], + x: reward.x, + y: reward.y, + yoyo: reward[RewardsConst.REWARDS_PARAMS]['yoyo'] + }, currentScene); + } + + static loadSpritesheet(reward, loader, gameConfig) + { + loader.spritesheet( + reward[RewardsConst.REWARDS_ASSET_KEY], + this.getSpritesheetPath(reward), + this.getRewardFrameConfig(reward[RewardsConst.REWARDS_PARAMS], gameConfig) + ); + } + + static getRewardFrameConfig(rewardParams, gameConfig) + { + return { + frameWidth: sc.get( + rewardParams, + 'frameWidth', + gameConfig.get('client/map/tileData/width', GameConst.GRAPHICS.FRAME_WIDTH) + ), + frameHeight: sc.get( + rewardParams, + 'frameHeight', + gameConfig.get('client/map/tileData/width', GameConst.GRAPHICS.FRAME_HEIGHT) + ) + }; + } + + static getSpritesheetPath(reward) + { + return RewardsConst.REWARDS_PATH + reward[RewardsConst.REWARDS_FILE] + '.png'; + } + + static removeRewardById(rewardId, gameManager) + { + if (!rewardId) { + return false; + } + let currentScene = gameManager.activeRoomEvents.getActiveScene(); + let rewardAnimation = sc.get(currentScene.objectsAnimations, rewardId, false); + if (!rewardAnimation) { + return false; + } + rewardAnimation.sceneSprite.destroy(); + delete currentScene.objectsAnimations[rewardId]; + } + + static validateParams(props) + { + let isValid = true; + if (!sc.get(props, 'currentScene', false)) { + Logger.error('Scene is undefined in Rewards Message Listener.'); + isValid = false; + } + if (!sc.get(props, 'gameConfig', false)) { + Logger.error('Game Config is undefined in Rewards Message Listener.'); + isValid = false; + } + if (!sc.get(props, 'objectPlugin', false)) { + Logger.error('Object Plugin is undefined in Rewards Message Listener.'); + isValid = false; + } + if (!sc.get(props, 'loader', false)) { + Logger.error('Loader is undefined in Rewards Message Listener.'); + isValid = false; + } + + return isValid; + } +} + +module.exports.RewardsMessageListener = RewardsMessageListener; \ No newline at end of file diff --git a/lib/rewards/constants.js b/lib/rewards/constants.js new file mode 100644 index 000000000..ed110df28 --- /dev/null +++ b/lib/rewards/constants.js @@ -0,0 +1,18 @@ +/** + * + * Reldens - RewardsConst + * + */ + +module.exports.RewardsConst = { + REWARDS_CONST: 'rw', + REWARDS_HAS_SPACE: 'hs', + REWARDS_TYPE: 'rt', + REWARDS_ASSET_KEY: 'rk', + REWARDS_FILE: 'rf', + REWARDS_PARAMS: 'rp', + REWARDS_PATH: 'assets/custom/sprites/', + REWARDS_PICK_UP_ACT: 'rpu', + DROP_EVENT_PREFIX: 'dep', + REMOVE_DROP: 'rd', +}; diff --git a/lib/rewards/server/entities-config.js b/lib/rewards/server/entities-config.js new file mode 100644 index 000000000..57bead5ff --- /dev/null +++ b/lib/rewards/server/entities-config.js @@ -0,0 +1,22 @@ +/** + * + * Reldens - Rewards Entities Config + * + */ + +const { RewardsEntity } = require('./entities/rewards-entity'); +const { RewardsModifiersEntity } = require('./entities/rewards-modifiers-entity'); +const { ObjectsItemsRewardsAnimationsEntity } = require('./entities/objects-items-rewards-animations-entity'); + +let objectsConfig = { + parentItemLabel: 'Rewards', + icon: 'WatsonHealth3DSoftware' +}; + +let entitiesConfig = { + rewards: RewardsEntity.propertiesConfig(objectsConfig), + rewardsModifiers: RewardsModifiersEntity.propertiesConfig(objectsConfig), + objectsItemsRewardsAnimations: ObjectsItemsRewardsAnimationsEntity.propertiesConfig(objectsConfig) +}; + +module.exports.entitiesConfig = entitiesConfig; diff --git a/lib/rewards/server/entities-translations.js b/lib/rewards/server/entities-translations.js new file mode 100644 index 000000000..41bc8e51b --- /dev/null +++ b/lib/rewards/server/entities-translations.js @@ -0,0 +1,13 @@ +/** + * + * Reldens - Rewards Entities Translations + * + */ + +module.exports.entitiesTranslations = { + labels: { + rewards: 'Rewards', + rewardsModifiers: 'Rewards - Modifiers', + objectsItemsRewardsAnimations: 'Rewards - Animations' + } +}; diff --git a/lib/rewards/server/entities/objects-items-rewards-animations-entity.js b/lib/rewards/server/entities/objects-items-rewards-animations-entity.js new file mode 100644 index 000000000..f685b5073 --- /dev/null +++ b/lib/rewards/server/entities/objects-items-rewards-animations-entity.js @@ -0,0 +1,55 @@ +/** + * + * Reldens - ObjectsItemsRewardsAnimationsEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); +const { sc } = require('@reldens/utils'); + +class ObjectsItemsRewardsAnimationsEntity extends EntityProperties +{ + static propertiesConfig(extraProps) + { + let properties = { + id: { + isId: true + }, + reward_id: { + type: 'reference', + reference: 'rewards', + isRequired: true + }, + asset_type: { + isRequired: true + }, + asset_key: { + isRequired: true + }, + file: { + isRequired: true + }, + extra_params: {} + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + + listPropertiesKeys = sc.removeFromArray(listPropertiesKeys, [ + 'file', + 'extra_params' + ]); + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.ObjectsItemsRewardsAnimationsEntity = ObjectsItemsRewardsAnimationsEntity; diff --git a/lib/rewards/server/entities/rewards-entity.js b/lib/rewards/server/entities/rewards-entity.js new file mode 100644 index 000000000..a75cb174d --- /dev/null +++ b/lib/rewards/server/entities/rewards-entity.js @@ -0,0 +1,66 @@ +/** + * + * Reldens - RewardsEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); + +class RewardsEntity extends EntityProperties +{ + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + object_id: { + type: 'reference', + reference: 'objects', + isRequired: true + }, + item_id: { + type: 'reference', + reference: 'items_item' + }, + modifier_id: { + type: 'reference', + reference: 'rewards_modifiers' + }, + experience: { + type: 'number' + }, + drop_rate: { + type: 'number', + isRequired: true + }, + drop_quantity: { + type: 'number', + isRequired: true + }, + is_unique: { + type: 'boolean' + }, + was_given: { + type: 'boolean' + }, + has_drop_body: { + type: 'boolean' + } + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.RewardsEntity = RewardsEntity; diff --git a/lib/rewards/server/entities/rewards-modifiers-entity.js b/lib/rewards/server/entities/rewards-modifiers-entity.js new file mode 100644 index 000000000..24522b4b2 --- /dev/null +++ b/lib/rewards/server/entities/rewards-modifiers-entity.js @@ -0,0 +1,70 @@ +/** + * + * Reldens - RewardsModifierEntity + * + */ + +const { EntityProperties } = require('../../../game/server/entity-properties'); +const { sc } = require('@reldens/utils'); + +class RewardsModifiersEntity extends EntityProperties +{ + static propertiesConfig(extraProps) + { + let properties = { + id: {}, + key: { + type: 'string', + isTitle: true, + isRequired: true + }, + property_key: { + type: 'string', + isRequired: true + }, + operation: { + availableValues: [ + { value: 1, label: 'Increment' }, + { value: 2, label: 'Decrease' }, + { value: 3, label: 'Divide' }, + { value: 4, label: 'Multiply' }, + { value: 5, label: 'Increment Percentage' }, + { value: 6, label: 'Decrease Percentage' }, + { value: 7, label: 'Set' }, + { value: 8, label: 'Method' }, + { value: 9, label: 'Set Number' } + ], + isRequired: true + }, + value: { + isRequired: true + }, + minValue: {}, + maxValue: {}, + minProperty: {}, + maxProperty: {} + }; + + let listPropertiesKeys = Object.keys(properties); + let editPropertiesKeys = [...listPropertiesKeys]; + + listPropertiesKeys = sc.removeFromArray(listPropertiesKeys, [ + 'minValue', + 'maxValue', + 'minProperty', + 'maxProperty' + ]); + editPropertiesKeys.splice(editPropertiesKeys.indexOf('id'), 1); + + return Object.assign({ + listProperties: listPropertiesKeys, + showProperties: Object.keys(properties), + filterProperties: listPropertiesKeys, + editProperties: editPropertiesKeys, + properties + }, extraProps); + } + +} + +module.exports.RewardsModifiersEntity = RewardsModifiersEntity; diff --git a/lib/rewards/server/message-actions.js b/lib/rewards/server/message-actions.js new file mode 100644 index 000000000..48515977d --- /dev/null +++ b/lib/rewards/server/message-actions.js @@ -0,0 +1,75 @@ +const { RewardsConst } = require('../constants'); +const { sc, Logger } = require('@reldens/utils'); +const { ObjectsConst } = require('../../objects/constants'); + +class RewardMessageActions +{ + + async executeMessageActions(client, message, room, playerSchema) + { + if (!sc.hasOwn(message, 'act') || !sc.hasOwn(message, 'type')) { + return false; + } + if (ObjectsConst.OBJECT_INTERACTION !== message.act || + RewardsConst.REWARDS_PICK_UP_ACT !== message.type) { + return false; + } + let rewardId = sc.get(message, 'id', false); + if (!rewardId) { + Logger.warning('A reward type message was received but without id'); + return false; + } + let rewardObject = sc.get(room.roomWorld.objectsManager.roomObjects, rewardId, false); + if (!rewardObject) { + Logger.error('Could not found reward ' + rewardId + ' on roomObjects'); + return false; + } + if (!rewardObject.isValidInteraction(playerSchema.state.x, playerSchema.state.y)) { + return false; + } + + let eventData = { + rewardObject, + client, + room, + playerSchema, + continueEvent: true + } + await room.events.emit('reldens.beforeRemovingDroppedReward', eventData); + if (!eventData.continueEvent) { + return false; + } + + await this.addItemToInventory(rewardObject.rewardModel, playerSchema); + this.removeDrop(room, rewardObject); + this.broadcastRemoveDrop(room, rewardObject); + return true; + } + + removeDrop(room, rewardObject) + { + room.roomWorld.removeBody(rewardObject.objectBody); + room.objectsManager.removeObjectData(rewardObject); + room.deleteObjectSceneData(rewardObject); + if (!room.objectsManager.hasRewardsDropped()) { + room.enableOnDispose(); + } + } + + broadcastRemoveDrop(room, rewardObject) { + room.broadcast('*', { + act: RewardsConst.REMOVE_DROP, + id: rewardObject.objectIndex + }); + } + + async addItemToInventory(rewardModel, playerSchema) + { + let item = await playerSchema.skillsServer.dataServer.getEntity('item').loadById(rewardModel.itemId); + let rewardItem = playerSchema.inventory.manager.createItemInstance(item.key); + return await playerSchema.inventory.manager.addItem(rewardItem); + } + +} + +module.exports.RewardMessageActions = RewardMessageActions; \ No newline at end of file diff --git a/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js b/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js new file mode 100644 index 000000000..fb3a66543 --- /dev/null +++ b/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js @@ -0,0 +1,34 @@ +/** + * + * Reldens - ObjectsItemsRewardsAnimationsModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); + +class ObjectsItemsRewardsAnimationsModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'objects_items_rewards_animations'; + } + + static get relationMappings() + { + const { RewardsModel } = require('./rewards-model'); + + return { + reward: { + relation: this.BelongsToOneRelation, + modelClass: RewardsModel, + join: { + from: this.tableName + '.reward_id', + to: RewardsModel.tableName + '.id' + } + } + }; + } +} + +module.exports.ObjectsItemsRewardsAnimationsModel = ObjectsItemsRewardsAnimationsModel; diff --git a/lib/rewards/server/models/objection-js/registered-models-objection-js.js b/lib/rewards/server/models/objection-js/registered-models-objection-js.js new file mode 100644 index 000000000..76008d60d --- /dev/null +++ b/lib/rewards/server/models/objection-js/registered-models-objection-js.js @@ -0,0 +1,23 @@ +/** + * + * Reldens - Registered Entities + * + */ + +const { RewardsModel } = require('./rewards-model'); +const { RewardsModifiersModel } = require('./rewards-modifiers-model'); +const { ObjectsItemsRewardsAnimationsModel } = require('./objects-items-rewards-animations-model'); +const { entitiesTranslations } = require('../../entities-translations'); +const { entitiesConfig } = require('../../entities-config'); + +let rawRegisteredEntities = { + rewards: RewardsModel, + rewardsModifiers: RewardsModifiersModel, + objectsItemsRewardsAnimations: ObjectsItemsRewardsAnimationsModel, +}; + +module.exports.rawRegisteredEntities = rawRegisteredEntities; + +module.exports.entitiesConfig = entitiesConfig; + +module.exports.entitiesTranslations = entitiesTranslations; diff --git a/lib/rewards/server/models/objection-js/rewards-model.js b/lib/rewards/server/models/objection-js/rewards-model.js new file mode 100644 index 000000000..e05bb8cb4 --- /dev/null +++ b/lib/rewards/server/models/objection-js/rewards-model.js @@ -0,0 +1,61 @@ +/** + * + * Reldens - RewardsModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); + +class RewardsModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'rewards'; + } + + static get relationMappings() + { + const { ItemModel } = require('@reldens/items-system/lib/server/storage/models/objection-js/item-model'); + const { ObjectsModel } = require('../../../../objects/server/models/objection-js/objects-model'); + const { RewardsModifiersModel } = require('./rewards-modifiers-model'); + const { ObjectsItemsRewardsAnimationsModel } = require('./objects-items-rewards-animations-model'); + + return { + objects: { + relation: this.HasOneRelation, + modelClass: ObjectsModel, + join: { + from: this.tableName + '.object_id', + to: ObjectsModel.tableName + '.id' + } + }, + items_item: { + relation: this.HasOneRelation, + modelClass: ItemModel, + join: { + from: this.tableName + '.item_id', + to: ItemModel.tableName + '.id' + } + }, + animations: { + relation: this.HasOneRelation, + modelClass: ObjectsItemsRewardsAnimationsModel, + join: { + from: this.tableName + '.id', + to: ObjectsItemsRewardsAnimationsModel.tableName + '.reward_id' + } + }, + modifier: { + relation: this.HasOneRelation, + modelClass: RewardsModifiersModel, + join: { + from: this.tableName + '.modifier_id', + to: RewardsModifiersModel.tableName + '.id' + } + } + }; + } +} + +module.exports.RewardsModel = RewardsModel; diff --git a/lib/rewards/server/models/objection-js/rewards-modifiers-model.js b/lib/rewards/server/models/objection-js/rewards-modifiers-model.js new file mode 100644 index 000000000..66f71b572 --- /dev/null +++ b/lib/rewards/server/models/objection-js/rewards-modifiers-model.js @@ -0,0 +1,34 @@ +/** + * + * Reldens - RewardsModifiersModel + * + */ + +const { ObjectionJsRawModel } = require('@reldens/storage'); + +class RewardsModifiersModel extends ObjectionJsRawModel +{ + + static get tableName() + { + return 'rewards_modifiers'; + } + + static get relationMappings() + { + const { RewardsModel } = require('./rewards-model'); + + return { + reward: { + relation: this.BelongsToOneRelation, + modelClass: RewardsModel, + join: { + from: this.tableName + '.id', + to: RewardsModel.tableName + '.modifier_id' + } + } + }; + } +} + +module.exports.RewardsModifiersModel = RewardsModifiersModel; diff --git a/lib/rewards/server/models/objects_items_rewards_animations.js b/lib/rewards/server/models/objects_items_rewards_animations.js new file mode 100644 index 000000000..36edd93e3 --- /dev/null +++ b/lib/rewards/server/models/objects_items_rewards_animations.js @@ -0,0 +1,38 @@ +const { sc } = require('@reldens/utils'); + +class ObjectsItemsRewardsAnimations +{ + id = 0; + rewardId = 0; + assetType = ''; + assetKey = ''; + file = ''; + extraParams = null; + + constructor(props) + { + this.id = props.id; + this.rewardId = props.rewardId; + this.assetType = props.assetType; + this.assetKey = props.assetKey; + this.file = props.file; + this.extraParams = props.extraParams; + } + + static fromModel(model) + { + if (!model) { + return null; + } + return new ObjectsItemsRewardsAnimations({ + 'id': model.id, + 'rewardId': model.reward_id, + 'assetType': model.asset_type, + 'assetKey': model.asset_key, + 'file': model.file, + 'extraParams': sc.toJson(model.extra_params), + }); + } +} + +module.exports.ObjectsItemsRewardsAnimations = ObjectsItemsRewardsAnimations; \ No newline at end of file diff --git a/lib/rewards/server/models/reward.js b/lib/rewards/server/models/reward.js new file mode 100644 index 000000000..9ffafc98c --- /dev/null +++ b/lib/rewards/server/models/reward.js @@ -0,0 +1,163 @@ +const { ObjectsItemsRewardsAnimations } = require('./objects_items_rewards_animations'); +const { sc } = require('@reldens/utils'); +const { ObjectsConst } = require('../../../objects/constants'); + +class Reward +{ + id = 0; + objectId = 0; + itemId = 0; + modifierId = 0; + experience = -1; + dropRate = -1; + dropQuantity = -1; + isUnique = false; + wasGiven = false; + hasDropBody = false; + animationData = null; + item = null; + modifier = null; + + constructor(param) + { + this.id = sc.get(param, 'id', 0); + this.objectId = sc.get(param, 'objectId', 0); + this.itemId = sc.get(param, 'itemId', 0); + this.modifierId = sc.get(param, 'modifierId', 0); + this.experience = sc.get(param, 'experience', -1); + this.dropRate = sc.get(param, 'dropRate', -1); + this.dropQuantity = sc.get(param, 'dropQuantity', -1); + this.isUnique = sc.get(param,'isUnique', 0) === 1; + this.wasGiven = sc.get(param,'wasGiven', 0) === 1; + this.hasDropBody = sc.get(param,'hasDropBody', 0) === 1; + this.animationData = param.animationData; + this.item = param.item; + this.modifier = param.modifier; + } + + isWinningReward() + { + if (0 === this.dropRate) { + return false; + } + return Math.floor(Math.random() * 100) <= this.dropRate; + } + + + isValidReward() + { + if (0 === this.dropQuantity) { + return false; + } + + if (this.isUnique && this.wasGiven) { + return false; + } + + if (!this.isItemType() && !this.isModifierType() && !this.hasExperienceSet()) { + return false; + } + + return true; + } + + isItemType() + { + return 0 !== Number(this.itemId); + } + + isModifierType() + { + return 0 !== Number(this.modifierId); + } + + hasExperienceSet() + { + return 0 <= this.experience; + } + + isDroppable() + { + return this.hasDropBody && sc.get(this, 'animationData', false); + } + + static areValidRewards(rewards) + { + return 0 < (rewards?.length || 0); + } + + static getRewardsBag(rewards) + { + let itemRangeArray = []; + + for (let reward of rewards) { + let itemRangeCount = itemRangeArray.length; + for (let i = 0; i < reward.dropRate; i++) { + itemRangeArray[itemRangeCount + i] = reward; + } + } + + return itemRangeArray; + } + + static fromModel(rewardModel) + { + if (!rewardModel) { + return null; + } + return new Reward({ + 'id': rewardModel.id, + 'objectId': rewardModel.object_id, + 'itemId': rewardModel.item_id, + 'modifierId': rewardModel.modifier_id, + 'experience': rewardModel.experience, + 'dropRate': rewardModel.drop_rate, + 'dropQuantity': rewardModel.drop_quantity, + 'isUnique': rewardModel.is_unique, + 'wasGiven': rewardModel.was_given, + 'hasDropBody': rewardModel.has_drop_body, + 'animationData': ObjectsItemsRewardsAnimations.fromModel(rewardModel.animations), + 'item': rewardModel.items_item, + 'modifier': rewardModel.modifier + }); + } + + static createDropObjectData(reward, roomId) + { + return { + id: reward.randomRewardId + reward.tileIndex, + room_id: roomId, + layer_name: reward.randomRewardId, + tile_index: reward.tileIndex, + object_class_key: ObjectsConst.TYPE_DROP, + client_key: reward.randomRewardId + reward.tileIndex, + rewardModel: reward, + asset_key: reward.animationData.assetKey, + client_params: JSON.stringify({ + frameStart: reward.animationData.extraParams.start, + frameEnd: reward.animationData.extraParams.end, + repeat: sc.get(reward.animationData.extraParams, 'repeat', -1), + hideOnComplete: false, + autoStart: true, + asset_key: reward.animationData.assetKey, + yoyo: sc.get(reward.animationData.extraParams, 'yoyo', false) + }), + enabled: 1, + objects_assets: [{ + object_asset_id: null, + object_id: reward.randomRewardId, + asset_type: reward.animationData.assetType, + asset_key: reward.animationData.assetKey, + file_1: reward.animationData.file, + file_2: null, + extra_params: JSON.stringify({ + frameWidth: reward.animationData.extraParams.frameWidth, + frameHeight: reward.animationData.extraParams.frameHeight + }) + }] + }; + } +} + + +module.exports.Reward = Reward; \ No newline at end of file diff --git a/lib/rewards/server/models/rewards-mapper.js b/lib/rewards/server/models/rewards-mapper.js new file mode 100644 index 000000000..8ebd93303 --- /dev/null +++ b/lib/rewards/server/models/rewards-mapper.js @@ -0,0 +1,19 @@ +const { Reward } = require('./reward'); + +class RewardsMapper +{ + static fromModels(rewardsModels) + { + if (0 === rewardsModels.length) { + return []; + } + + let mapped = []; + for (let rewardModel of rewardsModels) { + mapped.push(Reward.fromModel(rewardModel)); + } + return mapped; + } +} + +module.exports.RewardsMapper = RewardsMapper; \ No newline at end of file diff --git a/lib/rewards/server/plugin.js b/lib/rewards/server/plugin.js new file mode 100644 index 000000000..bda4c1c94 --- /dev/null +++ b/lib/rewards/server/plugin.js @@ -0,0 +1,53 @@ +/** + * + * Reldens - Rewards Server Plugin + * + */ +const { PluginInterface } = require('../../features/plugin-interface'); +const { sc, ErrorManager } = require('@reldens/utils'); +const { ObjectSubscriber } = require('./subscribers/object-subscriber'); +const { RewardsSubscriber } = require('./subscribers/rewards-subscriber'); +const { WorldDropSubscriber } = require('./subscribers/world-drop-subscriber'); +const { RewardMessageActions } = require('./message-actions'); +const { SenderDropSubscriber } = require('./subscribers/sender-drop-subscriber'); + +class RewardsPlugin extends PluginInterface +{ + + setup(props) + { + this.events = sc.get(props, 'events', false); + if (!this.events) { + ErrorManager.error('EventsManager undefined in RewardsPlugin.'); + } + + this.events.on('reldens.afterRunAdditionalRespawnSetup', async (event) => { + await ObjectSubscriber.enrichWithRewards(event.objInstance); + }); + + this.events.on('reldens.battleEnded', async (event) => { + let { playerSchema, pve } = event; + await RewardsSubscriber.giveRewards(playerSchema, pve?.targetObject, this.events); + }); + + this.events.on('reldens.sceneRoomOnCreate', async (roomScene) => { + this.events.on('reldens.afterGiveRewards', async (rewardEventData) => { + let rewards = await WorldDropSubscriber.createRewardItemObjectsOnRoom({ + roomScene, + rewardEventData + }); + if (!rewards) { + return; + } + roomScene.disableOnDispose(); + SenderDropSubscriber.sendDropsToClient(rewards, roomScene); + }); + }); + // when the client sent a message to any room it will be checked by all the global messages defined: + this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { + roomMessageActions.rewards = new RewardMessageActions(); + }); + } +} + +module.exports.RewardsPlugin = RewardsPlugin; diff --git a/lib/rewards/server/subscribers/object-subscriber.js b/lib/rewards/server/subscribers/object-subscriber.js new file mode 100644 index 000000000..5a86fe4c4 --- /dev/null +++ b/lib/rewards/server/subscribers/object-subscriber.js @@ -0,0 +1,24 @@ +/** + * + * Reldens - ObjectSubscriber + * + */ +const { RewardsMapper } = require('../models/rewards-mapper'); + + +class ObjectSubscriber +{ + static async enrichWithRewards(objectInstance) + { + objectInstance['rewards'] = RewardsMapper.fromModels( + await objectInstance.dataServer.getEntity('rewards').loadByWithRelations( + 'object_id', + objectInstance.id, + ['animations', 'items_item', 'modifier'] + ) + ); + } + +} + +module.exports.ObjectSubscriber = ObjectSubscriber; diff --git a/lib/rewards/server/subscribers/rewards-subscriber.js b/lib/rewards/server/subscribers/rewards-subscriber.js new file mode 100644 index 000000000..d2620944c --- /dev/null +++ b/lib/rewards/server/subscribers/rewards-subscriber.js @@ -0,0 +1,173 @@ +const { sc, Logger } = require('@reldens/utils'); +const { Modifier, ModifierConst } = require('@reldens/modifiers'); +const { Reward } = require('../models/reward'); + +class RewardsSubscriber +{ + static async giveRewards(playerSchema, targetObject, eventsManager) + { + let eventDrop = { + playerSchema, + targetObject, + continueEvent: true + }; + await eventsManager.emit('reldens.beforeGiveRewards', eventDrop); + if (!eventDrop.continueEvent) { + return; + } + + let rewards = sc.get(targetObject, 'rewards', false); + let winningRewards = this.getWinningRewards(rewards); + if (0 === winningRewards.length) { + return; + } + + let itemRewards = []; + for (let winningReward of winningRewards) { + let giveRewardResults = await this.giveReward(winningReward, playerSchema, async (item) => { + itemRewards.push({ + item, + reward: winningReward + }); + }); + if (0 < giveRewardResults.length) { + Logger.error('There were error(s) on the reward ' + winningReward.id + + ' given to the player ' + playerSchema.id, giveRewardResults); + } + } + + await this.updateUniqueRewards(winningRewards, targetObject.dataServer); + if (0 === itemRewards.length) { + return; + } + await eventsManager.emit('reldens.afterGiveRewards', { + itemRewards, + playerSchema, + targetObject, + winningRewards + }); + } + + static async giveReward(reward, playerSchema, callbackFn) + { + let results = []; + if (reward.isItemType()) { + let result = await this.applyItemReward(reward, playerSchema, callbackFn); + if (!result.isSuccess) { + results.push(result); + } + } + + if (reward.isModifierType()) { + let result = await this.applyModifierReward(reward, playerSchema); + if (!result.isSuccess) { + results.push(result); + } + } + + if (reward.hasExperienceSet()) { + let result = await this.applyExperienceReward(reward, playerSchema); + if (!result.isSuccess) { + results.push(result); + } + } + return results; + } + + static createRewardResult(isSuccess, message = '') + { + return { isSuccess, message }; + } + + static async applyItemReward(reward, playerSchema, callbackFn) + { + let winningItem = reward.item; + if (!winningItem) { + return this.createRewardResult(false, + 'The item with id ' + reward.itemId + ' was not found.'); + } + if (reward.isDroppable()) { + callbackFn(winningItem); + return this.createRewardResult(true); + } + let addItemsResult = await this.addItemToPlayerInventory(playerSchema, winningItem, reward.dropQuantity); + if (!addItemsResult) { + return this.createRewardResult(false, + 'Could not add item to Players Inventory - Reward ' + + reward.id + ' - Item Id = ' + reward.itemId); + } + return this.createRewardResult(true); + } + + static async applyModifierReward(reward, player) + { + let rewardModifierModel = reward.modifier; + if (!rewardModifierModel) { + return this.createRewardResult(false, + 'The modifier with id ' + reward.modifierId + ' was not found.'); + } + let modifier = new Modifier(rewardModifierModel); + modifier.apply(player); + if (ModifierConst.MOD_APPLIED !== modifier.state) { + return this.createRewardResult(false, 'Could not add modifier to Player - Reward ' + + reward.id + ' - Modifier Id = ' + reward.modifierId); + } + return this.createRewardResult(true); + } + + static async applyExperienceReward(reward, player) + { + await player.skillsServer.classPath.addExperience(reward.experience); + return this.createRewardResult(true); + } + + static async addItemToPlayerInventory(playerSchema, item, quantity) + { + let rewardItem = playerSchema.inventory.manager.createItemInstance(item.key, quantity); + if (!rewardItem) { + Logger.error(`Couldn't create item instance with key ${item.key}`); + return false; + } + return await playerSchema.inventory.manager.addItem(rewardItem); + } + + static getWinningRewards(rewards, usesRewardBag = false) + { + let rewardAwarded = []; + if (!Reward.areValidRewards(rewards)) { + return rewardAwarded; + } + if (usesRewardBag) { + let rewardsBag = Reward.getRewardsBag(rewards); + let reward = this.pickReward(rewardsBag); + if (reward.isValidReward()) { + rewardAwarded.push(reward); + } + return rewardAwarded; + } + for (let reward of rewards) { + if (!reward.isWinningReward() || !reward.isValidReward()) { + continue; + } + rewardAwarded.push(reward); + } + return rewardAwarded; + } + + static pickReward(rewardsBag) + { + return rewardsBag[Math.floor(Math.random() * rewardsBag.length)]; + } + + static async updateUniqueRewards(rewards, dataServer) + { + let rewardsRepository = dataServer.getEntity('rewards'); + for (let reward of rewards) { + if (reward.isUnique && !reward.was_given) { + await rewardsRepository.updateById(reward.id, { was_given: 1 }); + } + } + } +} + +module.exports.RewardsSubscriber = RewardsSubscriber; diff --git a/lib/rewards/server/subscribers/sender-drop-subscriber.js b/lib/rewards/server/subscribers/sender-drop-subscriber.js new file mode 100644 index 000000000..0c1ae76f3 --- /dev/null +++ b/lib/rewards/server/subscribers/sender-drop-subscriber.js @@ -0,0 +1,27 @@ +const { RewardsConst } = require('../../constants'); + +class SenderDropSubscriber +{ + + static sendDropsToClient(rewards, client) + { + let messageData = { + [RewardsConst.REWARDS_CONST]: {} + }; + for (let reward of rewards) { + messageData[RewardsConst.REWARDS_CONST][reward.randomRewardId + reward.tileIndex] = + { + [RewardsConst.REWARDS_TYPE]: reward.animationData.assetType, + [RewardsConst.REWARDS_ASSET_KEY]: reward.animationData.assetKey, + [RewardsConst.REWARDS_FILE]: reward.animationData.file, + [RewardsConst.REWARDS_PARAMS]: reward.animationData.extraParams, + x: reward.rewardPosition.x, + y: reward.rewardPosition.y + }; + } + // @TODO - BETA - Add new event here for the data. + client.broadcast('*', messageData); + } +} + +module.exports.SenderDropSubscriber = SenderDropSubscriber; \ No newline at end of file diff --git a/lib/rewards/server/subscribers/world-drop-subscriber.js b/lib/rewards/server/subscribers/world-drop-subscriber.js new file mode 100644 index 000000000..55869b39c --- /dev/null +++ b/lib/rewards/server/subscribers/world-drop-subscriber.js @@ -0,0 +1,145 @@ +const { sc, Logger } = require('@reldens/utils'); +const { Reward } = require('../models/reward'); +const { RewardMessageActions } = require('../message-actions'); + +class WorldDropSubscriber +{ + static async createRewardItemObjectsOnRoom(params) + { + let validParams = this.validateParams(params); + if (!validParams) { + return false; + } + let { targetObjectBody, itemRewards, roomScene } = validParams; + if (!itemRewards) { + return false; + } + let closerWalkableNodes = this.generateWalkableNodesAround(targetObjectBody); + + let rewards = []; + for (let { reward } of itemRewards) { + for (let i = 0; i < reward.dropQuantity; i++) { + let newReward = sc.deepMergeProperties({}, reward); + // @TODO - BETA - Check reward required properties. + newReward['rewardPosition'] = this.getRewardPosition(closerWalkableNodes); + newReward['tileIndex'] = this.getTileIndex(newReward.rewardPosition, targetObjectBody.worldWidth); + newReward['randomRewardId'] = this.getRandomRewardId(); + newReward['objectBody'] = await this.createRewardRoomObject(roomScene, targetObjectBody, newReward); + roomScene.addObjectStateSceneData(newReward['objectBody']); + if (!newReward['objectBody']) { + Logger.error('No object body for reward ' + newReward.id + ' could be created'); + continue; + } + this.setRewardTimeDisappear(newReward['objectBody'], roomScene); + rewards.push(newReward); + } + } + return rewards; + } + + static getRandomRewardId() + { + return `reward[${sc.randomChars(8)}]`; + } + + static generateWalkableNodesAround(targetObjectBody) + { + let { currentCol: col, currentRow: row } = targetObjectBody; + let pathfinder = targetObjectBody.getPathFinder(); + let firstNode = pathfinder.grid.getNodeAt(col, row); + let firstWorldPos = this.calculateWorldPosFromNode(firstNode, pathfinder.world.mapJson); + let nodes = [firstWorldPos]; + for (let i = -1; i <= 1; i++) { + for (let j = -1; j <= 1; j++) { + let node = pathfinder.grid.getNodeAt(col + i, row + j); + // node.tileIndex = node.x * pathfinder.world.mapJson.width + node.y; + if (node.walkable) { + nodes.push(this.calculateWorldPosFromNode(node, pathfinder.world.mapJson)); + } + } + } + return nodes; + } + + static calculateWorldPosFromNode(node, mapJson) + { + let tileW = mapJson.tilewidth, + tileH = mapJson.tileheight, + halfTileW = tileW / 2, + halfTileH = tileH / 2; + return { + x: node.x * tileW + halfTileW, + y: node.y * tileH + halfTileH + }; + + } + + static hasEnoughFreeNodesForRewards(itemRewards, nodes) + { + return nodes.length >= itemRewards.length; + } + + static getTileIndex(node, worldWidth) + { + return node.x * worldWidth + node.y; + } + + static getRewardPosition(closerWalkableNodes) + { + return closerWalkableNodes.splice(sc.randomInteger(0, closerWalkableNodes.length - 1), 1)[0]; + } + + static async createRewardRoomObject(roomScene, targetObjectBody, reward) + { + let dropObjectData = Reward.createDropObjectData(reward, roomScene.roomId); + await roomScene.objectsManager.generateObjectFromObjectData(dropObjectData); + return await roomScene.roomWorld.createRoomObjectBody( + { name: reward.randomRewardId }, + reward.tileIndex, + targetObjectBody.worldTileWidth, + targetObjectBody.worldTileHeight, + reward.rewardPosition.x, + reward.rewardPosition.y + ); + } + + static validateParams(params) + { + let rewardEventData = sc.get(params, 'rewardEventData', false); + if (!rewardEventData) { + Logger.critical('RewardEventData not found on WorldDropSubscriber'); + return false; + } + let roomScene = sc.get(params, 'roomScene', false); + if (!roomScene) { + Logger.critical('RoomScene not found on WorldDropSubscriber'); + return false; + } + let itemRewards = sc.get(rewardEventData, 'itemRewards', false); + + let targetObjectBody = sc.get(rewardEventData.targetObject, 'objectBody', false); + if (!targetObjectBody) { + Logger.critical('target_object objectBody not found on WorldDropSubscriber'); + return false; + } + return { + roomScene, + itemRewards, + targetObjectBody + }; + } + + static setRewardTimeDisappear(dropObject, roomScene) + { + setTimeout(() => { + if (!roomScene.objectsManager.getObjectData(dropObject.objectIndex)) { + return false; + } + let rewardMessageActions = new RewardMessageActions(); + rewardMessageActions.removeDrop(roomScene, dropObject); + rewardMessageActions.broadcastRemoveDrop(roomScene, dropObject); + }, roomScene.config.get('server/rewards/actions/disappearTime')); + } +} + +module.exports.WorldDropSubscriber = WorldDropSubscriber; \ No newline at end of file diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index 187af2f96..f643ad896 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -538,6 +538,36 @@ class RoomScene extends RoomLogin } } + addObjectStateSceneData(object) + { + let stateSceneData = sc.toJson(this.state.sceneData); + stateSceneData.objectsAnimationsData[object.objectIndex] = this.roomData.objectsAnimationsData[object.objectIndex]; + for (let objectAsset of object.objects_assets) { + stateSceneData.preloadAssets.push(objectAsset); + } + this.state.sceneData = JSON.stringify(stateSceneData); + } + + deleteObjectSceneData(object) + { + let stateSceneData = sc.toJson(this.state.sceneData); + delete stateSceneData.objectsAnimationsData[object.objectIndex]; + for (let objectAsset of object.objects_assets) { + stateSceneData.preloadAssets = stateSceneData.preloadAssets + .filter(obj => obj.object_id !== objectAsset.object_id); + } + this.state.sceneData = JSON.stringify(stateSceneData) + } + + disableOnDispose() + { + this.autoDispose = false; + } + + enableOnDispose() + { + this.autoDispose = true; + } } module.exports.RoomScene = RoomScene; diff --git a/lib/world/server/p2world.js b/lib/world/server/p2world.js index 5a2b678e2..3d1734818 100644 --- a/lib/world/server/p2world.js +++ b/lib/world/server/p2world.js @@ -169,7 +169,7 @@ class P2world extends World if(roomObject.multiple){ return; } - await this.createWorldObject(roomObject, objectIndex, tileW, tileH, posX, posY); + return await this.createWorldObject(roomObject, objectIndex, tileW, tileH, posX, posY); } markPathFinderTile(layer, isZeroTile, isChangePoint, isCollisionBody, c, r) @@ -241,7 +241,7 @@ class P2world extends World } // by default objects won't have mass: let bodyMass = sc.get(roomObject, 'bodyMass', 0); - let collision = sc.hasOwn(roomObject, 'collisionResponse', false); + let collision = sc.get(roomObject, 'collisionResponse', false); let hasState = sc.get(roomObject, 'hasState', false); let bodyObject = this.createCollisionBody(tileW, tileH, posX, posY, bodyMass, collision, hasState, objectIndex); bodyObject.isRoomObject = true; @@ -270,6 +270,7 @@ class P2world extends World posY, pathFinder }); + return roomObject; } createLimits() diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 249e97cb3..18e0ea27b 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -141,22 +141,70 @@ ALTER TABLE `items_item` ADD CONSTRAINT `FK_items_item_items_group` FOREIGN KEY ALTER TABLE `items_item_modifiers` ADD CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; ALTER TABLE `objects_items_inventory` ADD CONSTRAINT `FK_objects_items_inventory_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; + +# Features: +INSERT INTO `features` VALUES (NULL, 'rewards', 'Rewards', 1); + # Rewards: +CREATE TABLE `rewards_modifiers` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` int unsigned NOT NULL, + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `minProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `modifier_id` (`key`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Reward Modifiers table.'; + CREATE TABLE `rewards` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `object_id` INT(10) UNSIGNED NOT NULL, - `item_id` INT(10) UNSIGNED NOT NULL, + `item_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `modifier_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `experience` INT(11) UNSIGNED NOT NULL DEFAULT 0, `drop_rate` INT(10) UNSIGNED NOT NULL, `drop_quantity` INT(10) UNSIGNED NOT NULL, - `is_unique` TINYINT(3) UNSIGNED NOT NULL, - `was_given` TINYINT(3) UNSIGNED NOT NULL, + `is_unique` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `was_given` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `has_drop_body` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `FK_rewards_items_item` (`item_id`) USING BTREE, INDEX `FK_rewards_objects` (`object_id`) USING BTREE, CONSTRAINT `FK_rewards_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `FK_rewards_rewards_modifiers` FOREIGN KEY (`modifier_id`) REFERENCES `rewards_modifiers` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT `FK_rewards_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; +CREATE TABLE `objects_items_rewards_animations` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `reward_id` INT(10) UNSIGNED NOT NULL, + `asset_type` varchar(255) NOT NULL, + `asset_key` varchar(255) NOT NULL, + `file` varchar(255) NOT NULL, + `extra_params` TEXT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `FK_objects_items_rewards_animations_rewards` (`reward_id`) USING BTREE, + CONSTRAINT `FK_objects_items_rewards_animations_rewards` FOREIGN KEY (`reward_id`) REFERENCES `rewards` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION +) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + + +INSERT INTO `rewards` VALUES + (1, 7, 2, null, 10, 100, 1, 0, 0, 1), + (2, 6, 2, null, 10, 100, 3, 0, 0, 1); + +INSERT INTO `objects_items_rewards_animations` VALUES + (1, 2, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'), + (2, 1, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'); + +# Drop Reward Interaction Distance Config +INSERT INTO `config` (scope, path, value, type) VALUES ('server', 'rewards/actions/interactionsDistance', '140', 2); +INSERT INTO `config` (scope, path, value, type) VALUES ('server', 'rewards/disappearTime', '1800000', 2); +INSERT INTO `config` (scope, path, value, type) VALUES ('client', 'rewards/titles/rewardMessage', 'You obtained %dropQuantity %itemLabel', 1); + ####################################################################################################################### SET FOREIGN_KEY_CHECKS = 1; diff --git a/theme/default/assets/custom/sprites/branch-sprite.png b/theme/default/assets/custom/sprites/branch-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ea395a6fe33234a952f31e9cb530e4de97357f GIT binary patch literal 2233 zcmV;q2uAmbP)KI+Ue?|7TtD7*Y3JwtJoHG3tg+IMJR2TPh}y95lln^gpVYUgye?Y{j+pwU?Ddo zk=o~H=Dp{A&pkO$&b>hdL4Z5RTPrIznXMn1Ew-1wt8c4zOVFPI6vq$V*p@FL8w4T5 zk;Vw4qQdxUo!)40&(VG!Oe+AF>C?8mXhQ~>Ae&y1#rO0NAL0eF4|Q}tZg$`Co`0ql zfP+y!pi?Jmii_5-b|wHqC?GmEyf`de0Dmv9@pJzXe;VYil@(`YBopbfgiJEmO)J3R z($9B^LIU2EE{V9hXZPkggB2F6KqEuLZ?v^2pkFgILT2-Yh_@9ytpET3Z@+ixqxOz& zPC^vF{H-0YESqIxs*llF87<^NxG(_rZCmw+EqSs!fgq4eHi?CTw-r2N0jyT5y7t?D z?^L#TEm|Hgsw#LnRV54w5FmuGsq0=!5crAs+;lr(x2d$F28DVkjO)iv-;%$gX_b!#+0;zb>+2F{~PzKBQddca374gQtRwBn{$>f#aP() z0s(L9(bHEh&-V-r1|#~-*VibM62-$Ut;%&De)QG%Gp$1iQ8#5L*UD02($&2KK&L=g z&ycCVrl~teqZxGSi!fscOr=rT|4ENV2knjRU>MLsoG3^xju2EP$BQd*8y?E%vB7Q~ zM`V-ye=LM4%$o}KnQOs--u%}r?fl0yMpYZkxggg1`ab%$6((lQCgEB8e4- zbNx9zZ5>Y<5JEWwL5u)QQ*bT-!11GR!jyP4PL|@Pr>#86l88SgC8xgb${`$CNK=@n6+EYc9OpYV4^rD*Hc)4YOr48`~i=JOtuI0y%}{Cr^$SgLP~W_&hd@7))-P!e*mY zHFR5?A+6>O4lyP)+U*Z6R$`s^J8*eM+N6Rlt@oNPkcsJ12c$iKK;SWjJUe>1=&IOyY=>p8x60LQ81|=VT4dv z&dT_TO@;F7!iZq3y;}?5%g$2|jQpOUSJ5^uTuSyD`k!nJgNj5tF|+vnh=f4Gty{X!&b}XYjs6r72qn93if5tj$s&UM{}2I#m3whcdlHX0&10R@2+>w zI;CKz3b1o~ZnVX0YNFC~UBGDoU5(L6h zX)Jtkv&DM#Mq~cynCXU#`TqFOZd8CPxKqJSDTqR$9D6!os5|#@75~wrr~4gmJ^(;$ zWC5WWHa1&q1ZZ?d7#`7sey|T(THBx?H;uk7JLRhvUx-@oO2Uq~Qvqb~go5WihB^`( z5p)2@ZGU9>A_(O%VBCfvZ{14ZurTO&*axNOZn|Y+ahC#|#ZwA)djS9dq!B^;O%t|~ z(Qzx7%s3RRO#og13v?y~=gVs5T>x)SVV+blsVT@=?yI!NnKarVF3T6jOgP;B<{{M9 zcf&t#&F}yEXElX+Qo$bSfJtOa1pBpmQ%R3@6nuQBK=T0@HR3LHz(SeA=7Ow(Jz4+& zfK^h_k-ib*@A^iJfKL!$Hd{#-V4+Q6b3sPI9xs49cN}>i1~-; zG}=-71P+VCxDdi;K~NA6Qd6YxX=Ob;daTKBXzB7wCnSma2gmKiF{OGC1c59N1pC30 zK0OQ!j{WosKn8D3Vb0);f;}=XKTA4Q+7f9*5Mdg(mE3z|0FxPqaFG&t0W8pa=bhaT z)IZQ1UnLds=rrmN3U=hV>wrm|Rlu|6xh(*Id#5l@;gRQng+l!U&GB$P_uZ&af1uMS zV6!i%dk+8r{{h>&f1kRLg~ Date: Thu, 30 Mar 2023 19:54:46 +0200 Subject: [PATCH 33/82] - Reldens - v4.0.0 - Post rewards merge fixes. --- .gitignore | 2 - lib/audio/client/manager.js | 1 + lib/rewards/client/plugin.js | 18 +- .../client/rewards-message-listener.js | 33 +- ...objects-items-rewards-animations-entity.js | 1 + lib/rewards/server/message-actions.js | 38 +- .../objects-items-rewards-animations-model.js | 2 +- .../models/objection-js/rewards-model.js | 2 +- .../objection-js/rewards-modifiers-model.js | 2 +- .../objects_items_rewards_animations.js | 29 +- lib/rewards/server/models/reward.js | 63 +- lib/rewards/server/models/rewards-mapper.js | 15 +- lib/rewards/server/plugin.js | 14 +- .../server/subscribers/object-subscriber.js | 3 +- .../server/subscribers/rewards-subscriber.js | 113 +- .../subscribers/sender-drop-subscriber.js | 30 +- .../subscribers/world-drop-subscriber.js | 49 +- .../development/20190923183906_v4.0.0.js | 10 + .../development/reldens-install-v4.0.0.sql | 1386 +++++++++++++++++ migrations/production/beta.26-sql-update.sql | 11 +- 20 files changed, 1612 insertions(+), 210 deletions(-) create mode 100644 migrations/development/20190923183906_v4.0.0.js create mode 100644 migrations/development/reldens-install-v4.0.0.sql diff --git a/.gitignore b/.gitignore index d0c1f5b53..7ec9e0d58 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ /.env /.idea /knexfile.js -/migrations/* -!/migrations/production /node_modules /npm-debug* /test diff --git a/lib/audio/client/manager.js b/lib/audio/client/manager.js index 5fcb4dcc8..ca2cee598 100644 --- a/lib/audio/client/manager.js +++ b/lib/audio/client/manager.js @@ -72,6 +72,7 @@ class AudioManager { let soundConfig = Object.assign({}, this.defaultAudioConfig, (audio.config || {})); if(!sc.hasOwn(onScene.cache.audio.entries.entries, audio.audio_key)){ + // @TODO - BETA - Check this error. Logger.error('Audio file does not exists. Audio key:', audio.audio_key); return false; } diff --git a/lib/rewards/client/plugin.js b/lib/rewards/client/plugin.js index 5d9e58edc..a08266011 100644 --- a/lib/rewards/client/plugin.js +++ b/lib/rewards/client/plugin.js @@ -1,14 +1,19 @@ -const { sc, Logger } = require('@reldens/utils'); +/** + * + * Reldens - Rewards Client Plugin + * + */ + const { PluginInterface } = require('../../features/plugin-interface'); const { RewardsMessageListener } = require('./rewards-message-listener'); +const { Logger, sc } = require('@reldens/utils'); class RewardsPlugin extends PluginInterface { setup(props) { - super.setup(props); - if (!this.validateProps(props)) { + if(!this.validateProps(props)){ return false; } this.events.on('reldens.joinedRoom', (room, gameManager) => { @@ -20,19 +25,18 @@ class RewardsPlugin extends PluginInterface { let isValid = true; this.gameManager = sc.get(props, 'gameManager', false); - if (!this.gameManager) { + if(!this.gameManager){ Logger.error('Game Manager undefined in RewardsPlugin.'); isValid = false; } this.events = sc.get(props, 'events', false); - if (!this.events) { + if(!this.events){ Logger.error('EventsManager undefined in RewardsPlugin.'); isValid = false; } return isValid; } - } -module.exports.RewardsPlugin = RewardsPlugin; \ No newline at end of file +module.exports.RewardsPlugin = RewardsPlugin; diff --git a/lib/rewards/client/rewards-message-listener.js b/lib/rewards/client/rewards-message-listener.js index 5b783c38c..642d719d0 100644 --- a/lib/rewards/client/rewards-message-listener.js +++ b/lib/rewards/client/rewards-message-listener.js @@ -1,6 +1,12 @@ -const { sc, Logger } = require('@reldens/utils'); +/** + * + * Reldens - RewardsMessageListener + * + */ + const { RewardsConst } = require('../constants'); const { GameConst } = require('../../game/constants'); +const { Logger, sc } = require('@reldens/utils'); class RewardsMessageListener { @@ -9,11 +15,10 @@ class RewardsMessageListener { room.onMessage('*', (message) => { let rewards = sc.get(message, RewardsConst.REWARDS_CONST, false); - if (rewards) { + if(rewards){ this.loadRewards(rewards, gameManager); } - - if (RewardsConst.REMOVE_DROP === message.act) { + if(RewardsConst.REMOVE_DROP === message.act){ this.removeRewardById(message.id, gameManager); } }); @@ -25,11 +30,10 @@ class RewardsMessageListener let gameConfig = gameManager.config; let objectPlugin = gameManager.getFeature('objects'); let loader = currentScene.load; - if (!this.validateParams({ currentScene, gameConfig, objectPlugin, loader })) { + if(!this.validateParams({currentScene, gameConfig, objectPlugin, loader})){ return false; } - - for (let [rewardId, reward] of Object.entries(rewards)) { + for(let [rewardId, reward] of Object.entries(rewards)){ this.loadSpritesheet(reward, loader, gameConfig); loader.once('complete', async () => { await this.createRewardAnimation(objectPlugin, reward, rewardId, currentScene); @@ -93,12 +97,12 @@ class RewardsMessageListener static removeRewardById(rewardId, gameManager) { - if (!rewardId) { + if(!rewardId){ return false; } let currentScene = gameManager.activeRoomEvents.getActiveScene(); let rewardAnimation = sc.get(currentScene.objectsAnimations, rewardId, false); - if (!rewardAnimation) { + if(!rewardAnimation){ return false; } rewardAnimation.sceneSprite.destroy(); @@ -108,25 +112,24 @@ class RewardsMessageListener static validateParams(props) { let isValid = true; - if (!sc.get(props, 'currentScene', false)) { + if(!sc.get(props, 'currentScene', false)){ Logger.error('Scene is undefined in Rewards Message Listener.'); isValid = false; } - if (!sc.get(props, 'gameConfig', false)) { + if(!sc.get(props, 'gameConfig', false)){ Logger.error('Game Config is undefined in Rewards Message Listener.'); isValid = false; } - if (!sc.get(props, 'objectPlugin', false)) { + if(!sc.get(props, 'objectPlugin', false)){ Logger.error('Object Plugin is undefined in Rewards Message Listener.'); isValid = false; } - if (!sc.get(props, 'loader', false)) { + if(!sc.get(props, 'loader', false)){ Logger.error('Loader is undefined in Rewards Message Listener.'); isValid = false; } - return isValid; } } -module.exports.RewardsMessageListener = RewardsMessageListener; \ No newline at end of file +module.exports.RewardsMessageListener = RewardsMessageListener; diff --git a/lib/rewards/server/entities/objects-items-rewards-animations-entity.js b/lib/rewards/server/entities/objects-items-rewards-animations-entity.js index f685b5073..b3c9b3125 100644 --- a/lib/rewards/server/entities/objects-items-rewards-animations-entity.js +++ b/lib/rewards/server/entities/objects-items-rewards-animations-entity.js @@ -9,6 +9,7 @@ const { sc } = require('@reldens/utils'); class ObjectsItemsRewardsAnimationsEntity extends EntityProperties { + static propertiesConfig(extraProps) { let properties = { diff --git a/lib/rewards/server/message-actions.js b/lib/rewards/server/message-actions.js index 48515977d..8908da0fa 100644 --- a/lib/rewards/server/message-actions.js +++ b/lib/rewards/server/message-actions.js @@ -1,33 +1,38 @@ +/** + * + * Reldens - RewardMessageActions + * + */ + const { RewardsConst } = require('../constants'); -const { sc, Logger } = require('@reldens/utils'); const { ObjectsConst } = require('../../objects/constants'); +const { Logger, sc } = require('@reldens/utils'); class RewardMessageActions { async executeMessageActions(client, message, room, playerSchema) { - if (!sc.hasOwn(message, 'act') || !sc.hasOwn(message, 'type')) { + if(!sc.hasOwn(message, 'act') || !sc.hasOwn(message, 'type')){ return false; } - if (ObjectsConst.OBJECT_INTERACTION !== message.act || - RewardsConst.REWARDS_PICK_UP_ACT !== message.type) { + if(ObjectsConst.OBJECT_INTERACTION !== message.act || + RewardsConst.REWARDS_PICK_UP_ACT !== message.type){ return false; } let rewardId = sc.get(message, 'id', false); - if (!rewardId) { + if(!rewardId){ Logger.warning('A reward type message was received but without id'); return false; } let rewardObject = sc.get(room.roomWorld.objectsManager.roomObjects, rewardId, false); - if (!rewardObject) { + if(!rewardObject){ Logger.error('Could not found reward ' + rewardId + ' on roomObjects'); return false; } - if (!rewardObject.isValidInteraction(playerSchema.state.x, playerSchema.state.y)) { + if(!rewardObject.isValidInteraction(playerSchema.state.x, playerSchema.state.y)){ return false; } - let eventData = { rewardObject, client, @@ -36,33 +41,26 @@ class RewardMessageActions continueEvent: true } await room.events.emit('reldens.beforeRemovingDroppedReward', eventData); - if (!eventData.continueEvent) { + if(!eventData.continueEvent){ return false; } - await this.addItemToInventory(rewardObject.rewardModel, playerSchema); this.removeDrop(room, rewardObject); - this.broadcastRemoveDrop(room, rewardObject); + room.broadcast('*', {act: RewardsConst.REMOVE_DROP, id: rewardObject.objectIndex}); return true; } removeDrop(room, rewardObject) { + // @TODO - BETA - Move the next 3 calls into a single method on the room class, like room.removeObject(). room.roomWorld.removeBody(rewardObject.objectBody); room.objectsManager.removeObjectData(rewardObject); room.deleteObjectSceneData(rewardObject); - if (!room.objectsManager.hasRewardsDropped()) { + if(!room.objectsManager.hasRewardsDropped()){ room.enableOnDispose(); } } - broadcastRemoveDrop(room, rewardObject) { - room.broadcast('*', { - act: RewardsConst.REMOVE_DROP, - id: rewardObject.objectIndex - }); - } - async addItemToInventory(rewardModel, playerSchema) { let item = await playerSchema.skillsServer.dataServer.getEntity('item').loadById(rewardModel.itemId); @@ -72,4 +70,4 @@ class RewardMessageActions } -module.exports.RewardMessageActions = RewardMessageActions; \ No newline at end of file +module.exports.RewardMessageActions = RewardMessageActions; diff --git a/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js b/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js index fb3a66543..c848572b1 100644 --- a/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js +++ b/lib/rewards/server/models/objection-js/objects-items-rewards-animations-model.js @@ -17,7 +17,6 @@ class ObjectsItemsRewardsAnimationsModel extends ObjectionJsRawModel static get relationMappings() { const { RewardsModel } = require('./rewards-model'); - return { reward: { relation: this.BelongsToOneRelation, @@ -29,6 +28,7 @@ class ObjectsItemsRewardsAnimationsModel extends ObjectionJsRawModel } }; } + } module.exports.ObjectsItemsRewardsAnimationsModel = ObjectsItemsRewardsAnimationsModel; diff --git a/lib/rewards/server/models/objection-js/rewards-model.js b/lib/rewards/server/models/objection-js/rewards-model.js index e05bb8cb4..5422faf68 100644 --- a/lib/rewards/server/models/objection-js/rewards-model.js +++ b/lib/rewards/server/models/objection-js/rewards-model.js @@ -20,7 +20,6 @@ class RewardsModel extends ObjectionJsRawModel const { ObjectsModel } = require('../../../../objects/server/models/objection-js/objects-model'); const { RewardsModifiersModel } = require('./rewards-modifiers-model'); const { ObjectsItemsRewardsAnimationsModel } = require('./objects-items-rewards-animations-model'); - return { objects: { relation: this.HasOneRelation, @@ -56,6 +55,7 @@ class RewardsModel extends ObjectionJsRawModel } }; } + } module.exports.RewardsModel = RewardsModel; diff --git a/lib/rewards/server/models/objection-js/rewards-modifiers-model.js b/lib/rewards/server/models/objection-js/rewards-modifiers-model.js index 66f71b572..590f580d3 100644 --- a/lib/rewards/server/models/objection-js/rewards-modifiers-model.js +++ b/lib/rewards/server/models/objection-js/rewards-modifiers-model.js @@ -17,7 +17,6 @@ class RewardsModifiersModel extends ObjectionJsRawModel static get relationMappings() { const { RewardsModel } = require('./rewards-model'); - return { reward: { relation: this.BelongsToOneRelation, @@ -29,6 +28,7 @@ class RewardsModifiersModel extends ObjectionJsRawModel } }; } + } module.exports.RewardsModifiersModel = RewardsModifiersModel; diff --git a/lib/rewards/server/models/objects_items_rewards_animations.js b/lib/rewards/server/models/objects_items_rewards_animations.js index 36edd93e3..031093f2c 100644 --- a/lib/rewards/server/models/objects_items_rewards_animations.js +++ b/lib/rewards/server/models/objects_items_rewards_animations.js @@ -1,27 +1,27 @@ +/** + * + * Reldens - ObjectsItemsRewardsAnimations + * + */ + const { sc } = require('@reldens/utils'); class ObjectsItemsRewardsAnimations { - id = 0; - rewardId = 0; - assetType = ''; - assetKey = ''; - file = ''; - extraParams = null; constructor(props) { - this.id = props.id; - this.rewardId = props.rewardId; - this.assetType = props.assetType; - this.assetKey = props.assetKey; - this.file = props.file; - this.extraParams = props.extraParams; + this.id = sc.get(props, 'id', 0); + this.rewardId = sc.get(props, 'rewardId', 0); + this.assetType = sc.get(props, 'assetType', ''); + this.assetKey = sc.get(props, 'assetKey', ''); + this.file = sc.get(props, 'file', ''); + this.extraParams = sc.get(props, 'extraParams', {}); } static fromModel(model) { - if (!model) { + if(!model){ return null; } return new ObjectsItemsRewardsAnimations({ @@ -33,6 +33,7 @@ class ObjectsItemsRewardsAnimations 'extraParams': sc.toJson(model.extra_params), }); } + } -module.exports.ObjectsItemsRewardsAnimations = ObjectsItemsRewardsAnimations; \ No newline at end of file +module.exports.ObjectsItemsRewardsAnimations = ObjectsItemsRewardsAnimations; diff --git a/lib/rewards/server/models/reward.js b/lib/rewards/server/models/reward.js index 9ffafc98c..4506c8d5c 100644 --- a/lib/rewards/server/models/reward.js +++ b/lib/rewards/server/models/reward.js @@ -1,63 +1,52 @@ +/** + * + * Reldens - Reward + * + */ + const { ObjectsItemsRewardsAnimations } = require('./objects_items_rewards_animations'); -const { sc } = require('@reldens/utils'); const { ObjectsConst } = require('../../../objects/constants'); +const { sc } = require('@reldens/utils'); class Reward { - id = 0; - objectId = 0; - itemId = 0; - modifierId = 0; - experience = -1; - dropRate = -1; - dropQuantity = -1; - isUnique = false; - wasGiven = false; - hasDropBody = false; - animationData = null; - item = null; - modifier = null; constructor(param) { this.id = sc.get(param, 'id', 0); this.objectId = sc.get(param, 'objectId', 0); - this.itemId = sc.get(param, 'itemId', 0); - this.modifierId = sc.get(param, 'modifierId', 0); + this.itemId = sc.get(param, 'itemId', null); + this.modifierId = sc.get(param, 'modifierId', null); this.experience = sc.get(param, 'experience', -1); this.dropRate = sc.get(param, 'dropRate', -1); this.dropQuantity = sc.get(param, 'dropQuantity', -1); - this.isUnique = sc.get(param,'isUnique', 0) === 1; - this.wasGiven = sc.get(param,'wasGiven', 0) === 1; - this.hasDropBody = sc.get(param,'hasDropBody', 0) === 1; - this.animationData = param.animationData; - this.item = param.item; - this.modifier = param.modifier; + this.isUnique = 1 === sc.get(param,'isUnique', 0); + this.wasGiven = 1 === sc.get(param,'wasGiven', 0); + this.hasDropBody = 1 === sc.get(param,'hasDropBody', 0); + this.animationData = sc.get(param, 'animationData', null); + this.item = sc.get(param, 'item', null); + this.modifier = sc.get(param, 'modifier', null); } isWinningReward() { - if (0 === this.dropRate) { + if(0 === this.dropRate){ return false; } return Math.floor(Math.random() * 100) <= this.dropRate; } - isValidReward() { - if (0 === this.dropQuantity) { + if(0 === this.dropQuantity){ return false; } - - if (this.isUnique && this.wasGiven) { + if(this.isUnique && this.wasGiven){ return false; } - - if (!this.isItemType() && !this.isModifierType() && !this.hasExperienceSet()) { + if(!this.isItemType() && !this.isModifierType() && !this.hasExperienceSet()){ return false; } - return true; } @@ -73,7 +62,7 @@ class Reward hasExperienceSet() { - return 0 <= this.experience; + return 0 < this.experience; } isDroppable() @@ -89,20 +78,18 @@ class Reward static getRewardsBag(rewards) { let itemRangeArray = []; - - for (let reward of rewards) { + for(let reward of rewards){ let itemRangeCount = itemRangeArray.length; - for (let i = 0; i < reward.dropRate; i++) { + for(let i = 0; i < reward.dropRate; i++){ itemRangeArray[itemRangeCount + i] = reward; } } - return itemRangeArray; } static fromModel(rewardModel) { - if (!rewardModel) { + if(!rewardModel){ return null; } return new Reward({ @@ -157,7 +144,7 @@ class Reward }] }; } -} +} -module.exports.Reward = Reward; \ No newline at end of file +module.exports.Reward = Reward; diff --git a/lib/rewards/server/models/rewards-mapper.js b/lib/rewards/server/models/rewards-mapper.js index 8ebd93303..dfda673fb 100644 --- a/lib/rewards/server/models/rewards-mapper.js +++ b/lib/rewards/server/models/rewards-mapper.js @@ -1,19 +1,26 @@ +/** + * + * Reldens - RewardsMapper + * + */ + const { Reward } = require('./reward'); class RewardsMapper { + static fromModels(rewardsModels) { - if (0 === rewardsModels.length) { + if(0 === rewardsModels.length){ return []; } - let mapped = []; - for (let rewardModel of rewardsModels) { + for(let rewardModel of rewardsModels){ mapped.push(Reward.fromModel(rewardModel)); } return mapped; } + } -module.exports.RewardsMapper = RewardsMapper; \ No newline at end of file +module.exports.RewardsMapper = RewardsMapper; diff --git a/lib/rewards/server/plugin.js b/lib/rewards/server/plugin.js index bda4c1c94..1dbe51508 100644 --- a/lib/rewards/server/plugin.js +++ b/lib/rewards/server/plugin.js @@ -3,13 +3,14 @@ * Reldens - Rewards Server Plugin * */ -const { PluginInterface } = require('../../features/plugin-interface'); -const { sc, ErrorManager } = require('@reldens/utils'); + const { ObjectSubscriber } = require('./subscribers/object-subscriber'); const { RewardsSubscriber } = require('./subscribers/rewards-subscriber'); const { WorldDropSubscriber } = require('./subscribers/world-drop-subscriber'); const { RewardMessageActions } = require('./message-actions'); const { SenderDropSubscriber } = require('./subscribers/sender-drop-subscriber'); +const { PluginInterface } = require('../../features/plugin-interface'); +const { ErrorManager, sc } = require('@reldens/utils'); class RewardsPlugin extends PluginInterface { @@ -17,37 +18,34 @@ class RewardsPlugin extends PluginInterface setup(props) { this.events = sc.get(props, 'events', false); - if (!this.events) { + if(!this.events){ ErrorManager.error('EventsManager undefined in RewardsPlugin.'); } - this.events.on('reldens.afterRunAdditionalRespawnSetup', async (event) => { await ObjectSubscriber.enrichWithRewards(event.objInstance); }); - this.events.on('reldens.battleEnded', async (event) => { let { playerSchema, pve } = event; await RewardsSubscriber.giveRewards(playerSchema, pve?.targetObject, this.events); }); - this.events.on('reldens.sceneRoomOnCreate', async (roomScene) => { this.events.on('reldens.afterGiveRewards', async (rewardEventData) => { let rewards = await WorldDropSubscriber.createRewardItemObjectsOnRoom({ roomScene, rewardEventData }); - if (!rewards) { + if(!rewards){ return; } roomScene.disableOnDispose(); SenderDropSubscriber.sendDropsToClient(rewards, roomScene); }); }); - // when the client sent a message to any room it will be checked by all the global messages defined: this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { roomMessageActions.rewards = new RewardMessageActions(); }); } + } module.exports.RewardsPlugin = RewardsPlugin; diff --git a/lib/rewards/server/subscribers/object-subscriber.js b/lib/rewards/server/subscribers/object-subscriber.js index 5a86fe4c4..0df6d9be8 100644 --- a/lib/rewards/server/subscribers/object-subscriber.js +++ b/lib/rewards/server/subscribers/object-subscriber.js @@ -3,11 +3,12 @@ * Reldens - ObjectSubscriber * */ -const { RewardsMapper } = require('../models/rewards-mapper'); +const { RewardsMapper } = require('../models/rewards-mapper'); class ObjectSubscriber { + static async enrichWithRewards(objectInstance) { objectInstance['rewards'] = RewardsMapper.fromModels( diff --git a/lib/rewards/server/subscribers/rewards-subscriber.js b/lib/rewards/server/subscribers/rewards-subscriber.js index d2620944c..a9b58b536 100644 --- a/lib/rewards/server/subscribers/rewards-subscriber.js +++ b/lib/rewards/server/subscribers/rewards-subscriber.js @@ -1,9 +1,16 @@ -const { sc, Logger } = require('@reldens/utils'); -const { Modifier, ModifierConst } = require('@reldens/modifiers'); +/** + * + * Reldens - RewardsSubscriber + * + */ + const { Reward } = require('../models/reward'); +const { Modifier, ModifierConst } = require('@reldens/modifiers'); +const { Logger, sc } = require('@reldens/utils'); class RewardsSubscriber { + static async giveRewards(playerSchema, targetObject, eventsManager) { let eventDrop = { @@ -12,32 +19,29 @@ class RewardsSubscriber continueEvent: true }; await eventsManager.emit('reldens.beforeGiveRewards', eventDrop); - if (!eventDrop.continueEvent) { + if(!eventDrop.continueEvent){ return; } - let rewards = sc.get(targetObject, 'rewards', false); let winningRewards = this.getWinningRewards(rewards); - if (0 === winningRewards.length) { + if(0 === winningRewards.length){ return; } - let itemRewards = []; - for (let winningReward of winningRewards) { + for(let winningReward of winningRewards){ let giveRewardResults = await this.giveReward(winningReward, playerSchema, async (item) => { - itemRewards.push({ - item, - reward: winningReward - }); + itemRewards.push({item, reward: winningReward}); }); - if (0 < giveRewardResults.length) { - Logger.error('There were error(s) on the reward ' + winningReward.id + - ' given to the player ' + playerSchema.id, giveRewardResults); + if(0 === giveRewardResults.length){ + Logger.error( + 'There was an error on the reward ' + winningReward.id +' given to the player ' + playerSchema.id, + winningReward, + giveRewardResults + ); } } - await this.updateUniqueRewards(winningRewards, targetObject.dataServer); - if (0 === itemRewards.length) { + if(0 === itemRewards.length){ return; } await eventsManager.emit('reldens.afterGiveRewards', { @@ -51,50 +55,43 @@ class RewardsSubscriber static async giveReward(reward, playerSchema, callbackFn) { let results = []; - if (reward.isItemType()) { + if(reward.isItemType()){ let result = await this.applyItemReward(reward, playerSchema, callbackFn); - if (!result.isSuccess) { + if(!result.isSuccess){ results.push(result); } } - - if (reward.isModifierType()) { + if(reward.isModifierType()){ let result = await this.applyModifierReward(reward, playerSchema); - if (!result.isSuccess) { + if(!result.isSuccess){ results.push(result); } } - - if (reward.hasExperienceSet()) { + if(reward.hasExperienceSet()){ let result = await this.applyExperienceReward(reward, playerSchema); - if (!result.isSuccess) { + if(!result.isSuccess){ results.push(result); } } return results; } - static createRewardResult(isSuccess, message = '') - { - return { isSuccess, message }; - } - static async applyItemReward(reward, playerSchema, callbackFn) { let winningItem = reward.item; - if (!winningItem) { - return this.createRewardResult(false, - 'The item with id ' + reward.itemId + ' was not found.'); + if(!winningItem){ + return this.createRewardResult(false, 'The item with id ' + reward.itemId + ' was not found.'); } - if (reward.isDroppable()) { + if(reward.isDroppable()){ callbackFn(winningItem); return this.createRewardResult(true); } let addItemsResult = await this.addItemToPlayerInventory(playerSchema, winningItem, reward.dropQuantity); - if (!addItemsResult) { - return this.createRewardResult(false, - 'Could not add item to Players Inventory - Reward ' + - reward.id + ' - Item Id = ' + reward.itemId); + if(!addItemsResult){ + return this.createRewardResult( + false, + 'Could not add item to Players Inventory - Reward ' + reward.id + ' - Item Id = ' + reward.itemId + ); } return this.createRewardResult(true); } @@ -102,15 +99,16 @@ class RewardsSubscriber static async applyModifierReward(reward, player) { let rewardModifierModel = reward.modifier; - if (!rewardModifierModel) { - return this.createRewardResult(false, - 'The modifier with id ' + reward.modifierId + ' was not found.'); + if(!rewardModifierModel){ + return this.createRewardResult(false, 'The modifier with id ' + reward.modifierId + ' was not found.'); } let modifier = new Modifier(rewardModifierModel); modifier.apply(player); - if (ModifierConst.MOD_APPLIED !== modifier.state) { - return this.createRewardResult(false, 'Could not add modifier to Player - Reward ' + - reward.id + ' - Modifier Id = ' + reward.modifierId); + if(ModifierConst.MOD_APPLIED !== modifier.state){ + return this.createRewardResult( + false, + 'Could not add modifier to Player - Reward ' + reward.id + ' - Modifier Id = ' + reward.modifierId + ); } return this.createRewardResult(true); } @@ -121,10 +119,15 @@ class RewardsSubscriber return this.createRewardResult(true); } + static createRewardResult(isSuccess, message = '') + { + return { isSuccess, message }; + } + static async addItemToPlayerInventory(playerSchema, item, quantity) { let rewardItem = playerSchema.inventory.manager.createItemInstance(item.key, quantity); - if (!rewardItem) { + if(!rewardItem){ Logger.error(`Couldn't create item instance with key ${item.key}`); return false; } @@ -134,19 +137,19 @@ class RewardsSubscriber static getWinningRewards(rewards, usesRewardBag = false) { let rewardAwarded = []; - if (!Reward.areValidRewards(rewards)) { + if(!Reward.areValidRewards(rewards)){ return rewardAwarded; } - if (usesRewardBag) { + if(usesRewardBag){ let rewardsBag = Reward.getRewardsBag(rewards); - let reward = this.pickReward(rewardsBag); - if (reward.isValidReward()) { + let reward = rewardsBag[Math.floor(Math.random() * rewardsBag.length)]; + if(reward.isValidReward()){ rewardAwarded.push(reward); } return rewardAwarded; } - for (let reward of rewards) { - if (!reward.isWinningReward() || !reward.isValidReward()) { + for(let reward of rewards){ + if(!reward.isWinningReward() || !reward.isValidReward()){ continue; } rewardAwarded.push(reward); @@ -154,20 +157,16 @@ class RewardsSubscriber return rewardAwarded; } - static pickReward(rewardsBag) - { - return rewardsBag[Math.floor(Math.random() * rewardsBag.length)]; - } - static async updateUniqueRewards(rewards, dataServer) { let rewardsRepository = dataServer.getEntity('rewards'); - for (let reward of rewards) { - if (reward.isUnique && !reward.was_given) { + for(let reward of rewards){ + if(reward.isUnique && !reward.was_given){ await rewardsRepository.updateById(reward.id, { was_given: 1 }); } } } + } module.exports.RewardsSubscriber = RewardsSubscriber; diff --git a/lib/rewards/server/subscribers/sender-drop-subscriber.js b/lib/rewards/server/subscribers/sender-drop-subscriber.js index 0c1ae76f3..b6e3bd4bf 100644 --- a/lib/rewards/server/subscribers/sender-drop-subscriber.js +++ b/lib/rewards/server/subscribers/sender-drop-subscriber.js @@ -1,3 +1,9 @@ +/** + * + * Reldens - SenderDropSubscriber + * + */ + const { RewardsConst } = require('../../constants'); class SenderDropSubscriber @@ -8,20 +14,20 @@ class SenderDropSubscriber let messageData = { [RewardsConst.REWARDS_CONST]: {} }; - for (let reward of rewards) { - messageData[RewardsConst.REWARDS_CONST][reward.randomRewardId + reward.tileIndex] = - { - [RewardsConst.REWARDS_TYPE]: reward.animationData.assetType, - [RewardsConst.REWARDS_ASSET_KEY]: reward.animationData.assetKey, - [RewardsConst.REWARDS_FILE]: reward.animationData.file, - [RewardsConst.REWARDS_PARAMS]: reward.animationData.extraParams, - x: reward.rewardPosition.x, - y: reward.rewardPosition.y - }; + for(let reward of rewards){ + messageData[RewardsConst.REWARDS_CONST][reward.randomRewardId + reward.tileIndex] = { + [RewardsConst.REWARDS_TYPE]: reward.animationData.assetType, + [RewardsConst.REWARDS_ASSET_KEY]: reward.animationData.assetKey, + [RewardsConst.REWARDS_FILE]: reward.animationData.file, + [RewardsConst.REWARDS_PARAMS]: reward.animationData.extraParams, + x: reward.rewardPosition.x, + y: reward.rewardPosition.y + }; } - // @TODO - BETA - Add new event here for the data. + // @TODO - BETA - Add new event. client.broadcast('*', messageData); } + } -module.exports.SenderDropSubscriber = SenderDropSubscriber; \ No newline at end of file +module.exports.SenderDropSubscriber = SenderDropSubscriber; diff --git a/lib/rewards/server/subscribers/world-drop-subscriber.js b/lib/rewards/server/subscribers/world-drop-subscriber.js index 55869b39c..6b53bb927 100644 --- a/lib/rewards/server/subscribers/world-drop-subscriber.js +++ b/lib/rewards/server/subscribers/world-drop-subscriber.js @@ -1,32 +1,41 @@ -const { sc, Logger } = require('@reldens/utils'); +/** + * + * Reldens - WorldDropSubscriber + * + */ + +const { Logger, sc } = require('@reldens/utils'); const { Reward } = require('../models/reward'); const { RewardMessageActions } = require('../message-actions'); +const {RewardsConst} = require("../../constants"); class WorldDropSubscriber { + static async createRewardItemObjectsOnRoom(params) { let validParams = this.validateParams(params); - if (!validParams) { + if(!validParams){ return false; } let { targetObjectBody, itemRewards, roomScene } = validParams; - if (!itemRewards) { + if(!itemRewards){ return false; } let closerWalkableNodes = this.generateWalkableNodesAround(targetObjectBody); let rewards = []; - for (let { reward } of itemRewards) { - for (let i = 0; i < reward.dropQuantity; i++) { + for(let { reward } of itemRewards){ + for(let i = 0; i < reward.dropQuantity; i++){ let newReward = sc.deepMergeProperties({}, reward); // @TODO - BETA - Check reward required properties. newReward['rewardPosition'] = this.getRewardPosition(closerWalkableNodes); newReward['tileIndex'] = this.getTileIndex(newReward.rewardPosition, targetObjectBody.worldWidth); - newReward['randomRewardId'] = this.getRandomRewardId(); + // @TODO - BETA - Replace "reward[]" by "reward-". + newReward['randomRewardId'] = `reward[${sc.randomChars(8)}]`; newReward['objectBody'] = await this.createRewardRoomObject(roomScene, targetObjectBody, newReward); roomScene.addObjectStateSceneData(newReward['objectBody']); - if (!newReward['objectBody']) { + if(!newReward['objectBody']){ Logger.error('No object body for reward ' + newReward.id + ' could be created'); continue; } @@ -37,11 +46,6 @@ class WorldDropSubscriber return rewards; } - static getRandomRewardId() - { - return `reward[${sc.randomChars(8)}]`; - } - static generateWalkableNodesAround(targetObjectBody) { let { currentCol: col, currentRow: row } = targetObjectBody; @@ -49,11 +53,11 @@ class WorldDropSubscriber let firstNode = pathfinder.grid.getNodeAt(col, row); let firstWorldPos = this.calculateWorldPosFromNode(firstNode, pathfinder.world.mapJson); let nodes = [firstWorldPos]; - for (let i = -1; i <= 1; i++) { - for (let j = -1; j <= 1; j++) { + for(let i = -1; i <= 1; i++){ + for(let j = -1; j <= 1; j++){ let node = pathfinder.grid.getNodeAt(col + i, row + j); // node.tileIndex = node.x * pathfinder.world.mapJson.width + node.y; - if (node.walkable) { + if(node.walkable){ nodes.push(this.calculateWorldPosFromNode(node, pathfinder.world.mapJson)); } } @@ -71,11 +75,11 @@ class WorldDropSubscriber x: node.x * tileW + halfTileW, y: node.y * tileH + halfTileH }; - } static hasEnoughFreeNodesForRewards(itemRewards, nodes) { + // @TODO - BETA - Unused method. return nodes.length >= itemRewards.length; } @@ -106,19 +110,19 @@ class WorldDropSubscriber static validateParams(params) { let rewardEventData = sc.get(params, 'rewardEventData', false); - if (!rewardEventData) { + if(!rewardEventData){ Logger.critical('RewardEventData not found on WorldDropSubscriber'); return false; } let roomScene = sc.get(params, 'roomScene', false); - if (!roomScene) { + if(!roomScene){ Logger.critical('RoomScene not found on WorldDropSubscriber'); return false; } let itemRewards = sc.get(rewardEventData, 'itemRewards', false); let targetObjectBody = sc.get(rewardEventData.targetObject, 'objectBody', false); - if (!targetObjectBody) { + if(!targetObjectBody){ Logger.critical('target_object objectBody not found on WorldDropSubscriber'); return false; } @@ -132,14 +136,15 @@ class WorldDropSubscriber static setRewardTimeDisappear(dropObject, roomScene) { setTimeout(() => { - if (!roomScene.objectsManager.getObjectData(dropObject.objectIndex)) { + if(!roomScene.objectsManager.getObjectData(dropObject.objectIndex)){ return false; } let rewardMessageActions = new RewardMessageActions(); rewardMessageActions.removeDrop(roomScene, dropObject); - rewardMessageActions.broadcastRemoveDrop(roomScene, dropObject); + roomScene.broadcast('*', {act: RewardsConst.REMOVE_DROP, id: dropObject.objectIndex}); }, roomScene.config.get('server/rewards/actions/disappearTime')); } + } -module.exports.WorldDropSubscriber = WorldDropSubscriber; \ No newline at end of file +module.exports.WorldDropSubscriber = WorldDropSubscriber; diff --git a/migrations/development/20190923183906_v4.0.0.js b/migrations/development/20190923183906_v4.0.0.js new file mode 100644 index 000000000..b316fa317 --- /dev/null +++ b/migrations/development/20190923183906_v4.0.0.js @@ -0,0 +1,10 @@ +const fs = require('fs'); + +exports.up = function(knex) { + let sql = fs.readFileSync(__dirname+'/reldens-install-v4.0.0.sql').toString(); + return knex.raw(sql); +}; + +exports.down = function(knex) { + // nothing to do in the first version. +}; diff --git a/migrations/development/reldens-install-v4.0.0.sql b/migrations/development/reldens-install-v4.0.0.sql new file mode 100644 index 000000000..697a77d0a --- /dev/null +++ b/migrations/development/reldens-install-v4.0.0.sql @@ -0,0 +1,1386 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- ServerPlugin version: 8.0.27 - MySQL Community ServerPlugin - GPL +-- ServerPlugin OS: Win64 +-- HeidiSQL Version: 11.3.0.6295 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table audio +CREATE TABLE IF NOT EXISTS `audio` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `audio_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `files_name` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `config` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `room_id` int unsigned DEFAULT NULL, + `category_id` int unsigned DEFAULT NULL, + `enabled` int unsigned DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `audio_key` (`audio_key`), + KEY `FK_audio_rooms` (`room_id`), + KEY `FK_audio_audio_categories` (`category_id`), + CONSTRAINT `FK_audio_audio_categories` FOREIGN KEY (`category_id`) REFERENCES `audio_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `FK_audio_rooms` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table audio: ~2 rows (approximately) +/*!40000 ALTER TABLE `audio` DISABLE KEYS */; +INSERT INTO `audio` (`id`, `audio_key`, `files_name`, `config`, `room_id`, `category_id`, `enabled`) VALUES + (3, 'footstep', 'footstep.mp3', NULL, NULL, 3, 1), + (4, 'ReldensTownAudio', 'reldens-town.mp3', '', 4, 1, 1); +/*!40000 ALTER TABLE `audio` ENABLE KEYS */; + +-- Dumping structure for table audio_categories +CREATE TABLE IF NOT EXISTS `audio_categories` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `category_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `category_label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `enabled` int NOT NULL DEFAULT '0', + `single_audio` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `category_key` (`category_key`), + UNIQUE KEY `category_label` (`category_label`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table audio_categories: ~2 rows (approximately) +/*!40000 ALTER TABLE `audio_categories` DISABLE KEYS */; +INSERT INTO `audio_categories` (`id`, `category_key`, `category_label`, `enabled`, `single_audio`) VALUES + (1, 'music', 'Music', 1, 1), + (3, 'sound', 'Sound', 1, 0); +/*!40000 ALTER TABLE `audio_categories` ENABLE KEYS */; + +-- Dumping structure for table audio_markers +CREATE TABLE IF NOT EXISTS `audio_markers` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `audio_id` int unsigned NOT NULL, + `marker_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `start` int unsigned NOT NULL, + `duration` int unsigned NOT NULL, + `config` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `audio_id_marker_key` (`audio_id`,`marker_key`), + KEY `audio_id` (`audio_id`), + CONSTRAINT `FK_audio_markers_audio` FOREIGN KEY (`audio_id`) REFERENCES `audio` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table audio_markers: ~41 rows (approximately) +/*!40000 ALTER TABLE `audio_markers` DISABLE KEYS */; +INSERT INTO `audio_markers` (`id`, `audio_id`, `marker_key`, `start`, `duration`, `config`) VALUES + (1, 4, 'ReldensTown', 0, 41, NULL), + (2, 3, 'journeyman_right', 0, 1, NULL), + (3, 3, 'journeyman_left', 0, 1, NULL), + (4, 3, 'journeyman_up', 0, 1, NULL), + (5, 3, 'journeyman_down', 0, 1, NULL), + (6, 3, 'r_journeyman_right', 0, 1, NULL), + (7, 3, 'r_journeyman_left', 0, 1, NULL), + (8, 3, 'r_journeyman_up', 0, 1, NULL), + (9, 3, 'r_journeyman_down', 0, 1, NULL), + (10, 3, 'sorcerer_right', 0, 1, NULL), + (11, 3, 'sorcerer_left', 0, 1, NULL), + (12, 3, 'sorcerer_up', 0, 1, NULL), + (13, 3, 'sorcerer_down', 0, 1, NULL), + (14, 3, 'r_sorcerer_right', 0, 1, NULL), + (15, 3, 'r_sorcerer_left', 0, 1, NULL), + (16, 3, 'r_sorcerer_up', 0, 1, NULL), + (17, 3, 'r_sorcerer_down', 0, 1, NULL), + (18, 3, 'warlock_right', 0, 1, NULL), + (19, 3, 'warlock_left', 0, 1, NULL), + (20, 3, 'warlock_up', 0, 1, NULL), + (21, 3, 'warlock_down', 0, 1, NULL), + (22, 3, 'r_warlock_right', 0, 1, NULL), + (23, 3, 'r_warlock_left', 0, 1, NULL), + (24, 3, 'r_warlock_up', 0, 1, NULL), + (25, 3, 'r_warlock_down', 0, 1, NULL), + (26, 3, 'swordsman_right', 0, 1, NULL), + (27, 3, 'swordsman_left', 0, 1, NULL), + (28, 3, 'swordsman_up', 0, 1, NULL), + (29, 3, 'swordsman_down', 0, 1, NULL), + (30, 3, 'r_swordsman_right', 0, 1, NULL), + (31, 3, 'r_swordsman_left', 0, 1, NULL), + (32, 3, 'r_swordsman_up', 0, 1, NULL), + (33, 3, 'r_swordsman_down', 0, 1, NULL), + (34, 3, 'warrior_right', 0, 1, NULL), + (35, 3, 'warrior_left', 0, 1, NULL), + (36, 3, 'warrior_up', 0, 1, NULL), + (37, 3, 'warrior_down', 0, 1, NULL), + (38, 3, 'r_warrior_right', 0, 1, NULL), + (39, 3, 'r_warrior_left', 0, 1, NULL), + (40, 3, 'r_warrior_up', 0, 1, NULL), + (41, 3, 'r_warrior_down', 0, 1, NULL); +/*!40000 ALTER TABLE `audio_markers` ENABLE KEYS */; + +-- Dumping structure for table audio_player_config +CREATE TABLE IF NOT EXISTS `audio_player_config` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int unsigned NOT NULL, + `category_id` int unsigned DEFAULT NULL, + `enabled` int unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `player_id_category_id` (`player_id`,`category_id`), + KEY `FK_audio_player_config_audio_categories` (`category_id`), + CONSTRAINT `FK_audio_player_config_audio_categories` FOREIGN KEY (`category_id`) REFERENCES `audio_categories` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_audio_player_config_players` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table audio_player_config: ~0 rows (approximately) +/*!40000 ALTER TABLE `audio_player_config` DISABLE KEYS */; +/*!40000 ALTER TABLE `audio_player_config` ENABLE KEYS */; + +-- Dumping structure for table chat +CREATE TABLE IF NOT EXISTS `chat` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int unsigned NOT NULL, + `room_id` int unsigned DEFAULT NULL, + `message` varchar(140) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `private_player_id` int unsigned DEFAULT NULL, + `message_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `message_time` timestamp NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`player_id`), + KEY `scene_id` (`room_id`), + KEY `private_user_id` (`private_player_id`), + CONSTRAINT `FK__players` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`), + CONSTRAINT `FK__players_2` FOREIGN KEY (`private_player_id`) REFERENCES `players` (`id`), + CONSTRAINT `FK__scenes` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table chat: ~0 rows (approximately) +/*!40000 ALTER TABLE `chat` DISABLE KEYS */; +/*!40000 ALTER TABLE `chat` ENABLE KEYS */; + +-- Dumping structure for table config +CREATE TABLE IF NOT EXISTS `config` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `value` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `type` varchar(2) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=253 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table config: ~236 rows (approximately) +/*!40000 ALTER TABLE `config` DISABLE KEYS */; +INSERT INTO `config` (`id`, `scope`, `path`, `value`, `type`) VALUES + (1, 'server', 'rooms/validation/valid', 'room_game,chat_global', 't'), + (2, 'server', 'players/initialState/room_id', '4', 'i'), + (3, 'server', 'players/initialState/x', '400', 'i'), + (4, 'server', 'players/initialState/y', '345', 'i'), + (5, 'server', 'players/initialState/dir', 'down', 't'), + (13, 'server', 'rooms/validation/enabled', '1', 'b'), + (14, 'server', 'rooms/world/gravity_enabled', '0', 'b'), + (16, 'server', 'players/size/width', '25', 'i'), + (17, 'server', 'players/size/height', '25', 'i'), + (18, 'server', 'general/controls/allow_simultaneous_keys', '1', 'b'), + (19, 'server', 'rooms/world/timestep', '0.04', 'i'), + (20, 'server', 'chat/messages/broadcast_join', '1', 'b'), + (21, 'server', 'chat/messages/broadcast_leave', '1', 'b'), + (22, 'server', 'chat/messages/global_enabled', '1', 'b'), + (23, 'server', 'chat/messages/global_allowed_roles', '1,9000', 't'), + (24, 'server', 'players/physicsBody/speed', '180', 'i'), + (25, 'client', 'players/animations/fadeDuration', '1000', 'i'), + (26, 'client', 'ui/playerBox/x', '50', 'i'), + (27, 'client', 'ui/playerStats/enabled', '1', 'b'), + (28, 'client', 'ui/controls/enabled', '1', 'b'), + (29, 'client', 'map/tileData/width', '16', 'i'), + (30, 'client', 'map/tileData/height', '16', 'i'), + (31, 'client', 'map/tileData/margin', '1', 'i'), + (32, 'client', 'map/tileData/spacing', '2', 'i'), + (33, 'client', 'players/size/width', '52', 'i'), + (34, 'client', 'players/size/height', '71', 'i'), + (35, 'client', 'general/animations/frameRate', '10', 'i'), + (36, 'client', 'map/layersDepth/belowPlayer', '0', 'i'), + (37, 'client', 'map/layersDepth/changePoints', '0', 'i'), + (38, 'client', 'ui/sceneLabel/enabled', '1', 'b'), + (39, 'client', 'general/controls/action_button_hold', '0', 'b'), + (40, 'client', 'ui/chat/x', '440', 'i'), + (41, 'client', 'ui/chat/y', '940', 'i'), + (42, 'server', 'players/actions/interactionDistance', '40', 'i'), + (43, 'server', 'objects/actions/interactionsDistance', '140', 'i'), + (44, 'client', 'ui/playerBox/enabled', '1', 'b'), + (45, 'client', 'ui/playerBox/y', '30', 'i'), + (46, 'client', 'ui/lifeBar/enabled', '1', 'b'), + (47, 'client', 'ui/uiTarget/x', '10', 'i'), + (48, 'client', 'ui/uiTarget/y', '85', 'i'), + (49, 'client', 'ui/sceneLabel/x', '250', 'i'), + (50, 'client', 'ui/sceneLabel/y', '20', 'i'), + (51, 'client', 'ui/controls/x', '120', 'i'), + (52, 'client', 'ui/controls/y', '390', 'i'), + (53, 'client', 'ui/playerStats/x', '430', 'i'), + (54, 'client', 'ui/playerStats/y', '20', 'i'), + (55, 'client', 'ui/loading/font', 'Verdana, Geneva, sans-serif', 't'), + (56, 'client', 'ui/loading/fontSize', '20px', 't'), + (57, 'client', 'ui/loading/assetsSize', '18px', 't'), + (58, 'client', 'ui/loading/loadingColor', '#ffffff', 't'), + (59, 'client', 'ui/loading/percentColor', '#666666', 't'), + (60, 'client', 'ui/loading/assetsColor', '#ffffff', 't'), + (61, 'client', 'ui/loading/showAssets', '1', 'b'), + (62, 'client', 'players/animations/basedOnPress', '1', 'b'), + (63, 'client', 'players/animations/diagonalHorizontal', '1', 'b'), + (64, 'client', 'ui/uiTarget/hideOnDialog', '0', 'b'), + (65, 'client', 'ui/uiTarget/enabled', '1', 'b'), + (66, 'client', 'ui/lifeBar/x', '5', 'i'), + (67, 'client', 'ui/lifeBar/y', '12', 'i'), + (68, 'client', 'ui/lifeBar/height', '5', 'i'), + (69, 'client', 'ui/lifeBar/width', '50', 'i'), + (70, 'client', 'ui/lifeBar/fixedPosition', '0', 'b'), + (71, 'server', 'rooms/world/tryClosestPath', '1', 'b'), + (72, 'server', 'actions/pvp/battleTimeOff', '20000', 'i'), + (73, 'server', 'actions/pvp/timerType', 'bt', 's'), + (74, 'server', 'enemies/initialStats/atk', '10', 'i'), + (75, 'server', 'enemies/initialStats/def', '10', 'i'), + (76, 'server', 'enemies/initialStats/dodge', '10', 'i'), + (77, 'server', 'enemies/initialStats/hp', '10', 'i'), + (78, 'server', 'enemies/initialStats/mp', '10', 'i'), + (79, 'server', 'enemies/initialStats/speed', '10', 'i'), + (80, 'server', 'enemies/initialStats/stamina', '10', 'i'), + (81, 'client', 'ui/pointer/show', '1', 'b'), + (82, 'server', 'enemies/defaultAttacks/attackBullet', '0', 'b'), + (83, 'client', 'players/size/topOffset', '20', 'i'), + (84, 'client', 'players/size/leftOffset', '0', 'i'), + (85, 'server', 'rooms/world/onlyWalkable', '1', 'b'), + (86, 'client', 'ui/screen/responsive', '1', 'b'), + (87, 'client', 'ui/uiTarget/responsiveY', '0', 'i'), + (88, 'client', 'ui/uiTarget/responsiveX', '0', 'i'), + (89, 'client', 'ui/inventory/enabled', '1', 'b'), + (90, 'client', 'ui/inventory/x', '380', 'i'), + (91, 'client', 'ui/inventory/y', '450', 'i'), + (92, 'client', 'ui/inventory/responsiveY', '0', 'i'), + (93, 'client', 'ui/inventory/responsiveX', '100', 'i'), + (94, 'client', 'ui/equipment/enabled', '1', 'b'), + (95, 'client', 'ui/equipment/x', '430', 'i'), + (96, 'client', 'ui/equipment/y', '90', 'i'), + (97, 'client', 'ui/equipment/responsiveY', '0', 'i'), + (98, 'client', 'ui/equipment/responsiveX', '100', 'i'), + (99, 'client', 'ui/lifeBar/responsiveY', '24', 'i'), + (100, 'client', 'ui/lifeBar/responsiveX', '1', 'i'), + (101, 'client', 'ui/sceneLabel/responsiveY', '0', 'i'), + (102, 'client', 'ui/sceneLabel/responsiveX', '50', 'i'), + (103, 'client', 'ui/playerStats/responsiveY', '0', 'i'), + (104, 'client', 'ui/playerStats/responsiveX', '100', 'i'), + (105, 'client', 'ui/playerBox/responsiveY', '0', 'i'), + (106, 'client', 'ui/playerBox/responsiveX', '0', 'i'), + (107, 'client', 'ui/controls/responsiveY', '100', 'i'), + (108, 'client', 'ui/controls/responsiveX', '0', 'i'), + (109, 'client', 'ui/chat/responsiveY', '100', 'i'), + (110, 'client', 'ui/chat/responsiveX', '100', 'i'), + (111, 'client', 'ui/chat/enabled', '1', 'b'), + (112, 'client', 'ui/npcDialog/x', '120', 'i'), + (113, 'client', 'ui/npcDialog/y', '100', 'i'), + (114, 'client', 'ui/npcDialog/responsiveX', '10', 'i'), + (115, 'client', 'ui/npcDialog/responsiveY', '10', 'i'), + (116, 'client', 'ui/maximum/x', '1280', 'i'), + (117, 'client', 'ui/maximum/y', '720', 'i'), + (118, 'client', 'ui/chat/defaultOpen', '0', 'b'), + (119, 'client', 'ui/chat/notificationBalloon', '1', 'b'), + (120, 'client', 'ui/chat/damageMessages', '1', 'b'), + (121, 'server', 'players/actions/initialClassPathId', '1', 'i'), + (122, 'server', 'enemies/initialStats/aim', '10', 'i'), + (123, 'client', 'actions/skills/affectedProperty', 'hp', 't'), + (124, 'client', 'ui/controls/opacityEffect', '1', 'b'), + (125, 'client', 'ui/skills/y', '390', 'i'), + (126, 'client', 'ui/skills/x', '230', 'i'), + (127, 'client', 'ui/skills/responsiveY', '100', 'i'), + (128, 'client', 'ui/skills/responsiveX', '0', 'i'), + (129, 'client', 'ui/skills/enabled', '1', 'b'), + (130, 'client', 'skills/animations/default_atk', '{"key":"default_atk","animationData":{"enabled":true,"type":"spritesheet","img":"default_atk","frameWidth":64,"frameHeight":64,"start":0,"end":4,"repeat":0}}', 'j'), + (131, 'client', 'skills/animations/default_bullet', '{"key":"default_bullet","animationData":{"enabled":true,"type":"spritesheet","img":"default_bullet","frameWidth":64,"frameHeight":64,"start":0,"end":2,"repeat":-1,"rate":1}}', 'j'), + (132, 'client', 'skills/animations/default_cast', '{"key": "default_cast","animationData":{"enabled":false,"type":"spritesheet","img":"default_cast","frameWidth":64,"frameHeight":64,"start":0,"end":3,"repeat":0}}', 'j'), + (133, 'client', 'skills/animations/default_death', '{"key":"default_death","animationData":{"enabled":true,"type":"spritesheet","img":"default_death","frameWidth":64,"frameHeight":64,"start":0,"end":1,"repeat":0,"rate":1}}', 'j'), + (134, 'client', 'skills/animations/default_hit', '{"key":"default_hit","animationData":{"enabled":true,"type":"spritesheet","img":"default_hit","frameWidth":64,"frameHeight":64,"start":0,"end":3,"repeat":0,"depthByPlayer":"above"}}', 'j'), + (135, 'client', 'ui/controls/defaultActionKey', '', 't'), + (136, 'client', 'players/animations/collideWorldBounds', '1', 'b'), + (137, 'server', 'rooms/world/bulletsStopOnPlayer', '1', 'b'), + (138, 'client', 'players/animations/fallbackImage', 'player-base', 't'), + (139, 'client', 'players/multiplePlayers/enabled', '1', 'b'), + (140, 'server', 'players/gameOver/timeOut', '10000', 'i'), + (141, 'client', 'ui/controls/tabTarget', '1', 'b'), + (142, 'client', 'ui/controls/disableContextMenu', '1', 'b'), + (143, 'client', 'ui/controls/primaryMove', '1', 'b'), + (144, 'client', 'ui/instructions/enabled', '1', 'b'), + (145, 'client', 'ui/instructions/responsiveX', '100', 'i'), + (146, 'client', 'ui/instructions/responsiveY', '100', 'i'), + (147, 'client', 'ui/instructions/x', '380', 'i'), + (148, 'client', 'ui/instructions/y', '940', 'i'), + (149, 'client', 'ui/players/showNames', '1', 'b'), + (157, 'client', 'ui/lifeBar/top', '5', 'i'), + (158, 'client', 'actions/damage/enabled', '1', 'b'), + (159, 'client', 'actions/damage/showAll', '0', 'b'), + (160, 'client', 'actions/damage/font', 'Verdana, Geneva, sans-serif', 't'), + (161, 'client', 'actions/damage/color', '#ff0000', 't'), + (162, 'client', 'actions/damage/duration', '600', 'i'), + (163, 'client', 'actions/damage/top', '50', 'i'), + (164, 'client', 'actions/damage/fontSize', '14', 'i'), + (165, 'client', 'actions/damage/stroke', '#000000', 't'), + (166, 'client', 'actions/damage/strokeThickness', '4', 'i'), + (167, 'client', 'actions/damage/shadowColor', 'rgba(0,0,0,0.7)', 't'), + (168, 'client', 'ui/lifeBar/fillStyle', '0xff0000', 't'), + (169, 'client', 'ui/lifeBar/lineStyle', '0xffffff', 't'), + (170, 'client', 'ui/lifeBar/showAllPlayers', '0', 'b'), + (171, 'client', 'ui/lifeBar/showEnemies', '1', 'b'), + (172, 'client', 'players/animations/defaultFrames/left/start', '3', 'i'), + (173, 'client', 'players/animations/defaultFrames/left/end', '5', 'i'), + (174, 'client', 'players/animations/defaultFrames/right/start', '6', 'i'), + (175, 'client', 'players/animations/defaultFrames/right/end', '8', 'i'), + (176, 'client', 'players/animations/defaultFrames/up/start', '9', 'i'), + (177, 'client', 'players/animations/defaultFrames/up/end', '11', 'i'), + (178, 'client', 'players/animations/defaultFrames/down/start', '0', 'i'), + (179, 'client', 'players/animations/defaultFrames/down/end', '2', 'i'), + (180, 'client', 'ui/minimap/enabled', '1', 'b'), + (181, 'client', 'ui/minimap/mapWidthDivisor', '1', 'i'), + (182, 'client', 'ui/minimap/mapHeightDivisor', '1', 'i'), + (183, 'client', 'ui/minimap/fixedWidth', '450', 'i'), + (184, 'client', 'ui/minimap/fixedHeight', '450', 'i'), + (185, 'client', 'ui/minimap/roundMap', '1', 'b'), + (186, 'client', 'ui/minimap/camX', '140', 'i'), + (187, 'client', 'ui/minimap/camY', '10', 'i'), + (188, 'client', 'ui/minimap/camBackgroundColor', 'rgba(0,0,0,0.6)', 't'), + (189, 'client', 'ui/minimap/camZoom', '0.35', 'i'), + (190, 'client', 'ui/minimap/roundMap', '1', 'b'), + (191, 'client', 'ui/minimap/addCircle', '1', 'b'), + (192, 'client', 'ui/minimap/circleX', '220', 'i'), + (193, 'client', 'ui/minimap/circleY', '88', 'i'), + (194, 'client', 'ui/minimap/circleRadio', '80.35', 'i'), + (195, 'client', 'ui/minimap/circleColor', 'rgb(0,0,0)', 't'), + (196, 'client', 'ui/minimap/circleAlpha', '1', 'i'), + (197, 'client', 'ui/minimap/circleStrokeLineWidth', '6', 'i'), + (198, 'client', 'ui/minimap/circleStrokeColor', '0', 'i'), + (199, 'client', 'ui/minimap/circleStrokeAlpha', '0.6', 'i'), + (200, 'client', 'ui/minimap/circleFillColor', '1', 'i'), + (201, 'client', 'ui/minimap/circleFillAlpha', '0', 'i'), + (202, 'client', 'ui/pointer/topOffSet', '16', 'i'), + (203, 'client', 'ui/minimap/responsiveX', '34', 'i'), + (204, 'client', 'ui/minimap/responsiveY', '2.4', 'i'), + (205, 'client', 'ui/minimap/x', '180', 'i'), + (206, 'client', 'ui/minimap/y', '10', 'i'), + (207, 'client', 'ui/settings/responsiveX', '100', 'i'), + (208, 'client', 'ui/settings/responsiveY', '100', 'i'), + (209, 'client', 'ui/settings/x', '940', 'i'), + (210, 'client', 'ui/settings/y', '280', 'i'), + (211, 'client', 'ui/settings/enabled', '1', 'b'), + (212, 'client', 'ui/lifeBar/showOnClick', '1', 'b'), + (213, 'client', 'rooms/selection/allowOnRegistration', '1', 'b'), + (214, 'client', 'rooms/selection/allowOnLogin', '1', 'b'), + (215, 'client', 'rooms/selection/registrationAvailableRooms', '*', 't'), + (216, 'client', 'rooms/selection/loginLastLocation', '1', 'b'), + (218, 'client', 'rooms/selection/loginAvailableRooms', '*', 't'), + (219, 'client', 'rooms/selection/loginLastLocationLabel', 'Last Location', 't'), + (220, 'client', 'players/tapMovement/enabled', '1', 'b'), + (221, 'client', 'ui/chat/overheadChat/enabled', '1', 'b'), + (222, 'client', 'chat/messages/characterLimit', '10', 'i'), + (223, 'client', 'chat/messages/characterLimitOverhead', '5', 'i'), + (224, 'client', 'ui/chat/overheadText/fontFamily', 'Verdana, Geneva, sans-serif', 't'), + (225, 'client', 'ui/chat/overheadText/fontSize', '12px', 't'), + (226, 'client', 'ui/chat/overheadText/fill', '#ffffff', 't'), + (227, 'client', 'ui/chat/overheadText/align', 'center', 't'), + (228, 'client', 'ui/chat/overheadText/stroke', 'rgba(0,0,0,0.7)', 't'), + (229, 'client', 'ui/chat/overheadText/strokeThickness', '20', 'i'), + (230, 'client', 'ui/chat/overheadText/shadowX', '5', 'i'), + (231, 'client', 'ui/chat/overheadText/shadowY', '5', 'i'), + (232, 'client', 'ui/chat/overheadText/shadowColor', 'rgba(0,0,0,0.7)', 't'), + (233, 'client', 'ui/chat/overheadText/shadowBlur', '5', 'i'), + (234, 'client', 'ui/chat/overheadText/depth', '200000', 'i'), + (235, 'client', 'ui/chat/overheadText/height', '15', 'i'), + (236, 'client', 'ui/chat/overheadText/textLength', '4', 'i'), + (237, 'client', 'ui/players/nameText/fontFamily', 'Verdana, Geneva, sans-serif', 't'), + (238, 'client', 'ui/players/nameText/fontSize', '12px', 't'), + (239, 'client', 'ui/players/nameText/fill', '#ffffff', 't'), + (240, 'client', 'ui/players/nameText/align', 'center', 't'), + (241, 'client', 'ui/players/nameText/stroke', '#000000', 't'), + (242, 'client', 'ui/players/nameText/strokeThickness', '4', 'i'), + (243, 'client', 'ui/players/nameText/shadowX', '5', 'i'), + (244, 'client', 'ui/players/nameText/shadowY', '5', 'i'), + (245, 'client', 'ui/players/nameText/shadowColor', 'rgba(0,0,0,0.7)', 't'), + (246, 'client', 'ui/players/nameText/shadowBlur', '5', 'i'), + (247, 'client', 'ui/players/nameText/depth', '200000', 'i'), + (248, 'client', 'ui/players/nameText/height', '-90', 'i'), + (249, 'client', 'ui/players/nameText/textLength', '4', 'i'), + (250, 'client', 'ui/chat/overheadChat/isTyping', '1', 'b'), + (251, 'client', 'ui/chat/overheadText/timeOut', '5000', 'i'), + (252, 'client', 'ui/chat/overheadChat/closeChatBoxAfterSend', '1', 'b'); +/*!40000 ALTER TABLE `config` ENABLE KEYS */; + +-- Dumping structure for table features +CREATE TABLE IF NOT EXISTS `features` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `is_enabled` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table features: ~10 rows (approximately) +/*!40000 ALTER TABLE `features` DISABLE KEYS */; +INSERT INTO `features` (`id`, `code`, `title`, `is_enabled`) VALUES + (1, 'chat', 'Chat', 1), + (2, 'objects', 'Objects', 1), + (3, 'respawn', 'Respawn', 1), + (4, 'inventory', 'Inventory', 1), + (5, 'firebase', 'Firebase', 1), + (6, 'actions', 'Actions', 1), + (7, 'users', 'Users', 1), + (8, 'audio', 'Audio', 1), + (9, 'rooms', 'Rooms', 1), + (10, 'admin', 'Admin', 1); +/*!40000 ALTER TABLE `features` ENABLE KEYS */; + +-- Dumping structure for table items_group +CREATE TABLE IF NOT EXISTS `items_group` ( + `id` int NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `description` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `files_name` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `sort` int DEFAULT NULL, + `items_limit` int NOT NULL DEFAULT '0', + `limit_per_item` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='The group table is to save the groups settings.'; + +-- Dumping data for table items_group: ~6 rows (approximately) +/*!40000 ALTER TABLE `items_group` DISABLE KEYS */; +INSERT INTO `items_group` (`id`, `key`, `label`, `description`, `files_name`, `sort`, `items_limit`, `limit_per_item`) VALUES + (1, 'weapon', 'Weapon', 'All kinds of weapons.', 'weapon.png', 2, 1, 0), + (2, 'shield', 'Shield', 'Protect with these items.', 'shield.png', 3, 1, 0), + (3, 'armor', 'Armor', '', 'armor.png', 4, 1, 0), + (4, 'boots', 'Boots', '', 'boots.png', 6, 1, 0), + (5, 'gauntlets', 'Gauntlets', '', 'gauntlets.png', 5, 1, 0), + (6, 'helmet', 'Helmet', '', 'helmet.png', 1, 1, 0); +/*!40000 ALTER TABLE `items_group` ENABLE KEYS */; + +-- Dumping structure for table items_inventory +CREATE TABLE IF NOT EXISTS `items_inventory` ( + `id` int NOT NULL AUTO_INCREMENT, + `owner_id` int NOT NULL, + `item_id` int NOT NULL, + `qty` int NOT NULL DEFAULT '0', + `remaining_uses` int DEFAULT NULL, + `is_active` int DEFAULT NULL COMMENT 'For example equipped or not equipped items.', + PRIMARY KEY (`id`), + KEY `FK_items_inventory_items_item` (`item_id`), + CONSTRAINT `FK_items_inventory_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Inventory table is to save the items for each owner.'; + +-- Dumping data for table items_inventory: ~0 rows (approximately) +/*!40000 ALTER TABLE `items_inventory` DISABLE KEYS */; +/*!40000 ALTER TABLE `items_inventory` ENABLE KEYS */; + +-- Dumping structure for table items_item +CREATE TABLE IF NOT EXISTS `items_item` ( + `id` int NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `type` int NOT NULL DEFAULT '0', + `group_id` int DEFAULT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `qty_limit` int NOT NULL DEFAULT '0' COMMENT 'Default 0 to unlimited qty.', + `uses_limit` int NOT NULL DEFAULT '1' COMMENT 'Default 1 use per item (0 = unlimited).', + `useTimeOut` int DEFAULT NULL, + `execTimeOut` int DEFAULT NULL, + `customData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `key` (`key`), + KEY `group_id` (`group_id`), + CONSTRAINT `FK_items_item_items_group` FOREIGN KEY (`group_id`) REFERENCES `items_group` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='List of all available items in the system.'; + +-- Dumping data for table items_item: ~6 rows (approximately) +/*!40000 ALTER TABLE `items_item` DISABLE KEYS */; +INSERT INTO `items_item` (`id`, `key`, `type`, `group_id`, `label`, `description`, `qty_limit`, `uses_limit`, `useTimeOut`, `execTimeOut`, `customData`) VALUES + (1, 'coins', 3, NULL, 'Coins', NULL, 0, 1, NULL, NULL, NULL), + (2, 'branch', 0, NULL, 'Tree branch', 'An useless tree branch (for now)', 0, 1, NULL, NULL, NULL), + (3, 'heal_potion_20', 5, NULL, 'Heal Potion', 'A heal potion that will restore 20 HP.', 0, 1, NULL, NULL, '{"removeAfterUse":true,"animationData":{"frameWidth":64,"frameHeight":64,"start":6,"end":11,"repeat":0,"hide":true,"destroyOnComplete":true,"usePlayerPosition":true,"closeInventoryOnUse":true,"followPlayer":true,"startsOnTarget":true}}'), + (4, 'axe', 4, 1, 'Axe', 'A short distance but powerful weapon.', 0, 0, NULL, NULL, '{"animationData":{"frameWidth":64,"frameHeight":64,"start":6,"end":11,"repeat":0,"hide":true,"destroyOnComplete":true,"usePlayerPosition":true,"closeInventoryOnUse":true,"followPlayer":true,"startsOnTarget":true}}'), + (5, 'spear', 4, 1, 'Spear', 'A short distance but powerful weapon.', 0, 0, NULL, NULL, '{"animationData":{"frameWidth":64,"frameHeight":64,"start":6,"end":11,"repeat":0,"hide":true,"destroyOnComplete":true,"usePlayerPosition":true,"closeInventoryOnUse":true,"followPlayer":true,"startsOnTarget":true}}'), + (6, 'magic_potion_20', 5, NULL, 'Magic Potion', 'A magic potion that will restore 20 MP.', 0, 1, NULL, NULL, '{"removeAfterUse":true,"animationData":{"frameWidth":64,"frameHeight":64,"start":6,"end":11,"repeat":0,"hide":true,"destroyOnComplete":true,"usePlayerPosition":true,"closeInventoryOnUse":true,"followPlayer":true,"startsOnTarget":true}}'); +/*!40000 ALTER TABLE `items_item` ENABLE KEYS */; + +-- Dumping structure for table items_item_modifiers +CREATE TABLE IF NOT EXISTS `items_item_modifiers` ( + `id` int NOT NULL AUTO_INCREMENT, + `item_id` int NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` int NOT NULL, + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `item_id` (`item_id`), + CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Modifiers is the way we will affect the item owner.'; + +-- Dumping data for table items_item_modifiers: ~4 rows (approximately) +/*!40000 ALTER TABLE `items_item_modifiers` DISABLE KEYS */; +INSERT INTO `items_item_modifiers` (`id`, `item_id`, `key`, `property_key`, `operation`, `value`, `maxProperty`) VALUES + (1, 4, 'atk', 'stats/atk', 5, '5', NULL), + (2, 3, 'heal_potion_20', 'stats/hp', 1, '20', 'statsBase/hp'), + (3, 5, 'atk', 'stats/atk', 5, '3', NULL), + (4, 6, 'magic_potion_20', 'stats/mp', 1, '20', 'statsBase/mp'); +/*!40000 ALTER TABLE `items_item_modifiers` ENABLE KEYS */; + +-- Dumping structure for table objects +CREATE TABLE IF NOT EXISTS `objects` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `room_id` int unsigned NOT NULL, + `layer_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `tile_index` int unsigned DEFAULT NULL, + `object_class_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `client_key` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `private_params` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `client_params` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `enabled` int NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `room_id_layer_name_tile_index` (`room_id`,`layer_name`,`tile_index`), + KEY `room_id` (`room_id`), + KEY `object_class_key` (`object_class_key`), + CONSTRAINT `FK_objects_rooms` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table objects: ~8 rows (approximately) +/*!40000 ALTER TABLE `objects` DISABLE KEYS */; +INSERT INTO `objects` (`id`, `room_id`, `layer_name`, `tile_index`, `object_class_key`, `client_key`, `title`, `private_params`, `client_params`, `enabled`) VALUES + (1, 4, 'ground-collisions', 444, 'door_1', 'door_house_1', '', NULL, NULL, 1), + (4, 4, 'ground-collisions', 951, 'door_2', 'door_house_2', '', NULL, NULL, 1), + (5, 4, 'house-collisions-over-player', 535, 'npc_1', 'people_town_1', 'Alfred', NULL, NULL, 1), + (6, 5, 'respawn-area-monsters-lvl-1-2', NULL, 'enemy_1', 'enemy_forest_1', 'Tree', NULL, '{"autoStart":true}', 1), + (7, 5, 'respawn-area-monsters-lvl-1-2', NULL, 'enemy_2', 'enemy_forest_2', 'Tree Punch', NULL, '{"autoStart":true}', 1), + (8, 4, 'house-collisions-over-player', 538, 'npc_2', 'healer_1', 'Mamon', NULL, NULL, 1), + (10, 4, 'house-collisions-over-player', 560, 'npc_3', 'merchant_1', 'Gimly', NULL, NULL, 1), + (12, 4, 'house-collisions-over-player', 562, 'npc_4', 'weapons_master_1', 'Barrik', NULL, NULL, 1); +/*!40000 ALTER TABLE `objects` ENABLE KEYS */; + +-- Dumping structure for table objects_animations +CREATE TABLE IF NOT EXISTS `objects_animations` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `object_id` int unsigned NOT NULL, + `animationKey` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `animationData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `object_id_animationKey` (`object_id`,`animationKey`), + KEY `id` (`id`) USING BTREE, + KEY `object_id` (`object_id`) USING BTREE, + CONSTRAINT `FK_objects_animations_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table objects_animations: ~4 rows (approximately) +/*!40000 ALTER TABLE `objects_animations` DISABLE KEYS */; +INSERT INTO `objects_animations` (`id`, `object_id`, `animationKey`, `animationData`) VALUES + (5, 6, 'respawn-area-monsters-lvl-1-2_6_right', '{"start":6,"end":8}'), + (6, 6, 'respawn-area-monsters-lvl-1-2_6_down', '{"start":0,"end":2}'), + (7, 6, 'respawn-area-monsters-lvl-1-2_6_left', '{"start":3,"end":5}'), + (8, 6, 'respawn-area-monsters-lvl-1-2_6_up', '{"start":9,"end":11}'); +/*!40000 ALTER TABLE `objects_animations` ENABLE KEYS */; + +-- Dumping structure for table objects_assets +CREATE TABLE IF NOT EXISTS `objects_assets` ( + `object_asset_id` int unsigned NOT NULL AUTO_INCREMENT, + `object_id` int unsigned NOT NULL, + `asset_type` varchar(255) NOT NULL, + `asset_key` varchar(255) NOT NULL, + `file_1` varchar(255) NOT NULL, + `file_2` varchar(255) DEFAULT NULL, + `extra_params` text, + PRIMARY KEY (`object_asset_id`), + KEY `object_id` (`object_id`), + CONSTRAINT `FK_objects_assets_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1; + +-- Dumping data for table objects_assets: ~8 rows (approximately) +/*!40000 ALTER TABLE `objects_assets` DISABLE KEYS */; +INSERT INTO `objects_assets` (`object_asset_id`, `object_id`, `asset_type`, `asset_key`, `file_1`, `file_2`, `extra_params`) VALUES + (1, 1, 'spritesheet', 'door_house_1', 'door-a-x2', NULL, '{"frameWidth":32,"frameHeight":58}'), + (2, 4, 'spritesheet', 'door_house_2', 'door-a-x2', NULL, '{"frameWidth":32,"frameHeight":58}'), + (3, 5, 'spritesheet', 'people_town_1', 'people-b-x2', NULL, '{"frameWidth":52,"frameHeight":71}'), + (5, 6, 'spritesheet', 'enemy_forest_1', 'monster-treant', NULL, '{"frameWidth":47,"frameHeight":50}'), + (6, 7, 'spritesheet', 'enemy_forest_2', 'monster-golem2', NULL, '{"frameWidth":47,"frameHeight":50}'), + (7, 5, 'spritesheet', 'healer_1', 'healer-1', NULL, '{"frameWidth":52,"frameHeight":71}'), + (9, 10, 'spritesheet', 'merchant_1', 'people-d-x2', NULL, '{"frameWidth":52,"frameHeight":71}'), + (10, 12, 'spritesheet', 'weapons_master_1', 'people-c-x2', NULL, '{"frameWidth":52,"frameHeight":71}'); +/*!40000 ALTER TABLE `objects_assets` ENABLE KEYS */; + +-- Dumping structure for table players +CREATE TABLE IF NOT EXISTS `players` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `user_id` int unsigned NOT NULL, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `FK_players_users` (`user_id`), + CONSTRAINT `FK_players_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table players: ~0 rows (approximately) +/*!40000 ALTER TABLE `players` DISABLE KEYS */; +/*!40000 ALTER TABLE `players` ENABLE KEYS */; + +-- Dumping structure for table players_state +CREATE TABLE IF NOT EXISTS `players_state` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int unsigned NOT NULL, + `room_id` int unsigned NOT NULL, + `x` int unsigned NOT NULL, + `y` int unsigned NOT NULL, + `dir` varchar(25) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_player_state_rooms` (`room_id`), + KEY `FK_player_state_player_stats` (`player_id`), + CONSTRAINT `FK_player_state_player_stats` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_player_state_rooms` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table players_state: ~0 rows (approximately) +/*!40000 ALTER TABLE `players_state` DISABLE KEYS */; +/*!40000 ALTER TABLE `players_state` ENABLE KEYS */; + +-- Dumping structure for table players_stats +CREATE TABLE IF NOT EXISTS `players_stats` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int unsigned NOT NULL, + `stat_id` int unsigned NOT NULL, + `base_value` int unsigned NOT NULL, + `value` int unsigned NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `player_id_stat_id` (`player_id`,`stat_id`) USING BTREE, + KEY `stat_id` (`stat_id`) USING BTREE, + KEY `user_id` (`player_id`) USING BTREE, + CONSTRAINT `FK_player_current_stats_players` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT `FK_players_current_stats_players_stats` FOREIGN KEY (`stat_id`) REFERENCES `stats` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table players_stats: ~0 rows (approximately) +/*!40000 ALTER TABLE `players_stats` DISABLE KEYS */; +/*!40000 ALTER TABLE `players_stats` ENABLE KEYS */; + +-- Dumping structure for table respawn +CREATE TABLE IF NOT EXISTS `respawn` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `object_id` int unsigned NOT NULL, + `respawn_time` int unsigned NOT NULL DEFAULT '0', + `instances_limit` int unsigned NOT NULL DEFAULT '0', + `layer` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `respawn_object_id` (`object_id`), + CONSTRAINT `FK_respawn_objects` FOREIGN KEY (`object_id`) REFERENCES `objects` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table respawn: ~2 rows (approximately) +/*!40000 ALTER TABLE `respawn` DISABLE KEYS */; +INSERT INTO `respawn` (`id`, `object_id`, `respawn_time`, `instances_limit`, `layer`) VALUES + (3, 6, 20000, 2, 'respawn-area-monsters-lvl-1-2'), + (4, 7, 10000, 3, 'respawn-area-monsters-lvl-1-2'); +/*!40000 ALTER TABLE `respawn` ENABLE KEYS */; + +-- Dumping structure for table rooms +CREATE TABLE IF NOT EXISTS `rooms` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `map_filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'The map JSON file name.', + `scene_images` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `room_class_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table rooms: ~5 rows (approximately) +/*!40000 ALTER TABLE `rooms` DISABLE KEYS */; +INSERT INTO `rooms` (`id`, `name`, `title`, `map_filename`, `scene_images`, `room_class_key`) VALUES + (2, 'ReldensHouse_1', 'House - 1', 'reldens-house-1', 'reldens-house-1', NULL), + (3, 'ReldensHouse_2', 'House - 2', 'reldens-house-2', 'reldens-house-2', NULL), + (4, 'ReldensTown', 'Town', 'reldens-town', 'reldens-town', NULL), + (5, 'ReldensForest', 'Forest', 'reldens-forest', 'reldens-forest', NULL), + (6, 'ReldensHouse_1b', 'House - 1 - Floor 2', 'reldens-house-1-2d-floor', 'reldens-house-1-2d-floor', NULL); +/*!40000 ALTER TABLE `rooms` ENABLE KEYS */; + +-- Dumping structure for table rooms_change_points +CREATE TABLE IF NOT EXISTS `rooms_change_points` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `room_id` int unsigned NOT NULL, + `tile_index` int unsigned NOT NULL, + `next_room_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + KEY `scene_id` (`room_id`), + KEY `FK_rooms_change_points_rooms_2` (`next_room_id`), + CONSTRAINT `FK_rooms_change_points_rooms` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_rooms_change_points_rooms_2` FOREIGN KEY (`next_room_id`) REFERENCES `rooms` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table rooms_change_points: ~14 rows (approximately) +/*!40000 ALTER TABLE `rooms_change_points` DISABLE KEYS */; +INSERT INTO `rooms_change_points` (`id`, `room_id`, `tile_index`, `next_room_id`) VALUES + (1, 2, 816, 4), + (2, 2, 817, 4), + (3, 3, 778, 4), + (4, 3, 779, 4), + (5, 4, 444, 2), + (6, 4, 951, 3), + (7, 4, 18, 5), + (8, 4, 19, 5), + (9, 5, 1315, 4), + (10, 5, 1316, 4), + (11, 2, 623, 6), + (12, 2, 663, 6), + (13, 6, 624, 2), + (14, 6, 664, 2); +/*!40000 ALTER TABLE `rooms_change_points` ENABLE KEYS */; + +-- Dumping structure for table rooms_return_points +CREATE TABLE IF NOT EXISTS `rooms_return_points` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `room_id` int unsigned NOT NULL, + `direction` varchar(5) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `x` int unsigned NOT NULL, + `y` int unsigned NOT NULL, + `is_default` int unsigned NOT NULL, + `from_room_id` int unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_scenes_return_points_rooms` (`room_id`), + KEY `FK_scenes_return_points_rooms_2` (`from_room_id`) USING BTREE, + CONSTRAINT `FK_rooms_return_points_rooms_from_room_id` FOREIGN KEY (`from_room_id`) REFERENCES `rooms` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT `FK_rooms_return_points_rooms_room_id` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table rooms_return_points: ~11 rows (approximately) +/*!40000 ALTER TABLE `rooms_return_points` DISABLE KEYS */; +INSERT INTO `rooms_return_points` (`id`, `room_id`, `direction`, `x`, `y`, `is_default`, `from_room_id`) VALUES + (1, 2, 'up', 548, 615, 0, 4), + (2, 3, 'up', 640, 600, 1, NULL), + (3, 4, 'down', 400, 345, 1, 2), + (4, 4, 'down', 1266, 670, 0, 3), + (5, 5, 'up', 640, 768, 0, 4), + (6, 5, 'up', 640, 768, 0, 4), + (7, 4, 'down', 615, 64, 0, 5), + (8, 4, 'down', 615, 64, 0, 5), + (9, 6, 'right', 820, 500, 0, 2), + (10, 6, 'right', 820, 500, 0, 2), + (11, 2, 'left', 720, 540, 0, 6); +/*!40000 ALTER TABLE `rooms_return_points` ENABLE KEYS */; + +-- Dumping structure for table skills_class_level_up_animations +CREATE TABLE IF NOT EXISTS `skills_class_level_up_animations` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `class_path_id` int unsigned DEFAULT NULL, + `level_id` int unsigned DEFAULT NULL, + `animationData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `class_path_id_level_id` (`class_path_id`,`level_id`) USING BTREE, + KEY `FK_skills_class_level_up_skills_levels` (`level_id`) USING BTREE, + CONSTRAINT `FK_skills_class_level_up_skills_class_path` FOREIGN KEY (`class_path_id`) REFERENCES `skills_class_path` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT `FK_skills_class_level_up_skills_levels` FOREIGN KEY (`level_id`) REFERENCES `skills_levels` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_class_level_up_animations: ~1 rows (approximately) +/*!40000 ALTER TABLE `skills_class_level_up_animations` DISABLE KEYS */; +INSERT INTO `skills_class_level_up_animations` (`id`, `class_path_id`, `level_id`, `animationData`) VALUES + (1, NULL, NULL, '{"enabled":true,"type":"spritesheet","img":"heal_cast","frameWidth":64,"frameHeight":70,"start":0,"end":3,"repeat":-1,"destroyTime":2000,"depthByPlayer":"above"}'); +/*!40000 ALTER TABLE `skills_class_level_up_animations` ENABLE KEYS */; + +-- Dumping structure for table skills_class_path +CREATE TABLE IF NOT EXISTS `skills_class_path` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `levels_set_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key` (`key`), + KEY `levels_set_id` (`levels_set_id`), + CONSTRAINT `FK_skills_class_path_skills_levels_set` FOREIGN KEY (`levels_set_id`) REFERENCES `skills_levels_set` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_class_path: ~5 rows (approximately) +/*!40000 ALTER TABLE `skills_class_path` DISABLE KEYS */; +INSERT INTO `skills_class_path` (`id`, `key`, `label`, `levels_set_id`) VALUES + (1, 'journeyman', 'Journeyman', 1), + (2, 'sorcerer', 'Sorcerer', 2), + (3, 'warlock', 'Warlock', 3), + (4, 'swordsman', 'Swordsman', 4), + (5, 'warrior', 'Warrior', 5); +/*!40000 ALTER TABLE `skills_class_path` ENABLE KEYS */; + +-- Dumping structure for table skills_class_path_level_labels +CREATE TABLE IF NOT EXISTS `skills_class_path_level_labels` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `class_path_id` int unsigned NOT NULL, + `level_id` int unsigned NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `class_path_id_level_key` (`class_path_id`,`level_id`) USING BTREE, + KEY `class_path_id` (`class_path_id`), + KEY `level_key` (`level_id`) USING BTREE, + CONSTRAINT `FK__skills_class_path` FOREIGN KEY (`class_path_id`) REFERENCES `skills_class_path` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_skills_class_path_level_labels_skills_levels` FOREIGN KEY (`level_id`) REFERENCES `skills_levels` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_class_path_level_labels: ~5 rows (approximately) +/*!40000 ALTER TABLE `skills_class_path_level_labels` DISABLE KEYS */; +INSERT INTO `skills_class_path_level_labels` (`id`, `class_path_id`, `level_id`, `label`) VALUES + (1, 1, 3, 'Old Traveler'), + (2, 2, 7, 'Fire Master'), + (3, 3, 11, 'Magus'), + (4, 4, 15, 'Blade Master'), + (5, 5, 19, 'Paladin'); +/*!40000 ALTER TABLE `skills_class_path_level_labels` ENABLE KEYS */; + +-- Dumping structure for table skills_class_path_level_skills +CREATE TABLE IF NOT EXISTS `skills_class_path_level_skills` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `class_path_id` int unsigned NOT NULL, + `level_id` int unsigned NOT NULL, + `skill_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `class_path_id` (`class_path_id`), + KEY `skill_id` (`skill_id`), + KEY `level_key` (`level_id`) USING BTREE, + CONSTRAINT `FK_skills_class_path_level_skills_skills_class_path` FOREIGN KEY (`class_path_id`) REFERENCES `skills_class_path` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_skills_class_path_level_skills_skills_levels` FOREIGN KEY (`level_id`) REFERENCES `skills_levels` (`key`) ON UPDATE CASCADE, + CONSTRAINT `FK_skills_class_path_level_skills_skills_levels_id` FOREIGN KEY (`level_id`) REFERENCES `skills_levels` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK_skills_class_path_level_skills_skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_class_path_level_skills: ~15 rows (approximately) +/*!40000 ALTER TABLE `skills_class_path_level_skills` DISABLE KEYS */; +INSERT INTO `skills_class_path_level_skills` (`id`, `class_path_id`, `level_id`, `skill_id`) VALUES + (1, 1, 1, 2), + (2, 1, 3, 1), + (3, 1, 4, 3), + (4, 1, 4, 4), + (5, 2, 5, 1), + (6, 2, 7, 3), + (7, 2, 8, 4), + (8, 3, 9, 1), + (9, 3, 11, 3), + (10, 3, 12, 2), + (11, 4, 13, 2), + (12, 4, 15, 4), + (13, 5, 17, 2), + (14, 5, 19, 1), + (15, 5, 20, 4); +/*!40000 ALTER TABLE `skills_class_path_level_skills` ENABLE KEYS */; + +-- Dumping structure for table skills_groups +CREATE TABLE IF NOT EXISTS `skills_groups` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `sort` int unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_groups: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_groups` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_groups` ENABLE KEYS */; + +-- Dumping structure for table skills_levels +CREATE TABLE IF NOT EXISTS `skills_levels` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` int unsigned NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `required_experience` bigint unsigned DEFAULT NULL, + `level_set_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_level_set_id` (`key`,`level_set_id`), + KEY `level_set_id` (`level_set_id`), + CONSTRAINT `FK_skills_levels_skills_levels_set` FOREIGN KEY (`level_set_id`) REFERENCES `skills_levels_set` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_levels: ~20 rows (approximately) +/*!40000 ALTER TABLE `skills_levels` DISABLE KEYS */; +INSERT INTO `skills_levels` (`id`, `key`, `label`, `required_experience`, `level_set_id`) VALUES + (1, 1, '1', 0, 1), + (2, 2, '2', 100, 1), + (3, 5, '5', 338, 1), + (4, 10, '10', 2570, 1), + (5, 1, '1', 0, 2), + (6, 2, '2', 100, 2), + (7, 5, '5', 338, 2), + (8, 10, '10', 2570, 2), + (9, 1, '1', 0, 3), + (10, 2, '2', 100, 3), + (11, 5, '5', 338, 3), + (12, 10, '10', 2570, 3), + (13, 1, '1', 0, 4), + (14, 2, '2', 100, 4), + (15, 5, '5', 338, 4), + (16, 10, '10', 2570, 4), + (17, 1, '1', 0, 5), + (18, 2, '2', 100, 5), + (19, 5, '5', 338, 5), + (20, 10, '10', 2570, 5); +/*!40000 ALTER TABLE `skills_levels` ENABLE KEYS */; + +-- Dumping structure for table skills_levels_modifiers +CREATE TABLE IF NOT EXISTS `skills_levels_modifiers` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `level_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` int unsigned NOT NULL, + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `minProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `modifier_id` (`key`) USING BTREE, + KEY `level_key` (`level_id`) USING BTREE, + CONSTRAINT `FK_skills_levels_modifiers_skills_levels` FOREIGN KEY (`level_id`) REFERENCES `skills_levels` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=121 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Modifiers table.'; + +-- Dumping data for table skills_levels_modifiers: ~120 rows (approximately) +/*!40000 ALTER TABLE `skills_levels_modifiers` DISABLE KEYS */; +INSERT INTO `skills_levels_modifiers` (`id`, `level_id`, `key`, `property_key`, `operation`, `value`, `minValue`, `maxValue`, `minProperty`, `maxProperty`) VALUES + (1, 2, 'inc_atk', 'stats/atk', 1, '10', NULL, NULL, NULL, NULL), + (2, 2, 'inc_def', 'stats/def', 1, '10', NULL, NULL, NULL, NULL), + (3, 2, 'inc_hp', 'stats/hp', 1, '10', NULL, NULL, NULL, NULL), + (4, 2, 'inc_mp', 'stats/mp', 1, '10', NULL, NULL, NULL, NULL), + (5, 2, 'inc_atk', 'statsBase/atk', 1, '10', NULL, NULL, NULL, NULL), + (6, 2, 'inc_def', 'statsBase/def', 1, '10', NULL, NULL, NULL, NULL), + (7, 2, 'inc_hp', 'statsBase/hp', 1, '10', NULL, NULL, NULL, NULL), + (8, 2, 'inc_mp', 'statsBase/mp', 1, '10', NULL, NULL, NULL, NULL), + (9, 3, 'inc_atk', 'stats/atk', 1, '20', NULL, NULL, NULL, NULL), + (10, 3, 'inc_def', 'stats/def', 1, '20', NULL, NULL, NULL, NULL), + (11, 3, 'inc_hp', 'stats/hp', 1, '20', NULL, NULL, NULL, NULL), + (12, 3, 'inc_mp', 'stats/mp', 1, '20', NULL, NULL, NULL, NULL), + (13, 3, 'inc_atk', 'statsBase/atk', 1, '20', NULL, NULL, NULL, NULL), + (14, 3, 'inc_def', 'statsBase/def', 1, '20', NULL, NULL, NULL, NULL), + (15, 3, 'inc_hp', 'statsBase/hp', 1, '20', NULL, NULL, NULL, NULL), + (16, 3, 'inc_mp', 'statsBase/mp', 1, '20', NULL, NULL, NULL, NULL), + (17, 4, 'inc_atk', 'stats/atk', 1, '50', NULL, NULL, NULL, NULL), + (18, 4, 'inc_def', 'stats/def', 1, '50', NULL, NULL, NULL, NULL), + (19, 4, 'inc_hp', 'stats/hp', 1, '50', NULL, NULL, NULL, NULL), + (20, 4, 'inc_mp', 'stats/mp', 1, '50', NULL, NULL, NULL, NULL), + (21, 4, 'inc_atk', 'statsBase/atk', 1, '50', NULL, NULL, NULL, NULL), + (22, 4, 'inc_def', 'statsBase/def', 1, '50', NULL, NULL, NULL, NULL), + (23, 4, 'inc_hp', 'statsBase/hp', 1, '50', NULL, NULL, NULL, NULL), + (24, 4, 'inc_mp', 'statsBase/mp', 1, '50', NULL, NULL, NULL, NULL), + (25, 6, 'inc_atk', 'stats/atk', 1, '10', NULL, NULL, NULL, NULL), + (26, 6, 'inc_def', 'stats/def', 1, '10', NULL, NULL, NULL, NULL), + (27, 6, 'inc_hp', 'stats/hp', 1, '10', NULL, NULL, NULL, NULL), + (28, 6, 'inc_mp', 'stats/mp', 1, '10', NULL, NULL, NULL, NULL), + (29, 6, 'inc_atk', 'statsBase/atk', 1, '10', NULL, NULL, NULL, NULL), + (30, 6, 'inc_def', 'statsBase/def', 1, '10', NULL, NULL, NULL, NULL), + (31, 6, 'inc_hp', 'statsBase/hp', 1, '10', NULL, NULL, NULL, NULL), + (32, 6, 'inc_mp', 'statsBase/mp', 1, '10', NULL, NULL, NULL, NULL), + (33, 7, 'inc_atk', 'stats/atk', 1, '20', NULL, NULL, NULL, NULL), + (34, 7, 'inc_def', 'stats/def', 1, '20', NULL, NULL, NULL, NULL), + (35, 7, 'inc_hp', 'stats/hp', 1, '20', NULL, NULL, NULL, NULL), + (36, 7, 'inc_mp', 'stats/mp', 1, '20', NULL, NULL, NULL, NULL), + (37, 7, 'inc_atk', 'statsBase/atk', 1, '20', NULL, NULL, NULL, NULL), + (38, 7, 'inc_def', 'statsBase/def', 1, '20', NULL, NULL, NULL, NULL), + (39, 7, 'inc_hp', 'statsBase/hp', 1, '20', NULL, NULL, NULL, NULL), + (40, 7, 'inc_mp', 'statsBase/mp', 1, '20', NULL, NULL, NULL, NULL), + (41, 8, 'inc_atk', 'stats/atk', 1, '50', NULL, NULL, NULL, NULL), + (42, 8, 'inc_def', 'stats/def', 1, '50', NULL, NULL, NULL, NULL), + (43, 8, 'inc_hp', 'stats/hp', 1, '50', NULL, NULL, NULL, NULL), + (44, 8, 'inc_mp', 'stats/mp', 1, '50', NULL, NULL, NULL, NULL), + (45, 8, 'inc_atk', 'statsBase/atk', 1, '50', NULL, NULL, NULL, NULL), + (46, 8, 'inc_def', 'statsBase/def', 1, '50', NULL, NULL, NULL, NULL), + (47, 8, 'inc_hp', 'statsBase/hp', 1, '50', NULL, NULL, NULL, NULL), + (48, 8, 'inc_mp', 'statsBase/mp', 1, '50', NULL, NULL, NULL, NULL), + (49, 10, 'inc_atk', 'stats/atk', 1, '10', NULL, NULL, NULL, NULL), + (50, 10, 'inc_def', 'stats/def', 1, '10', NULL, NULL, NULL, NULL), + (51, 10, 'inc_hp', 'stats/hp', 1, '10', NULL, NULL, NULL, NULL), + (52, 10, 'inc_mp', 'stats/mp', 1, '10', NULL, NULL, NULL, NULL), + (53, 10, 'inc_atk', 'statsBase/atk', 1, '10', NULL, NULL, NULL, NULL), + (54, 10, 'inc_def', 'statsBase/def', 1, '10', NULL, NULL, NULL, NULL), + (55, 10, 'inc_hp', 'statsBase/hp', 1, '10', NULL, NULL, NULL, NULL), + (56, 10, 'inc_mp', 'statsBase/mp', 1, '10', NULL, NULL, NULL, NULL), + (57, 11, 'inc_atk', 'stats/atk', 1, '20', NULL, NULL, NULL, NULL), + (58, 11, 'inc_def', 'stats/def', 1, '20', NULL, NULL, NULL, NULL), + (59, 11, 'inc_hp', 'stats/hp', 1, '20', NULL, NULL, NULL, NULL), + (60, 11, 'inc_mp', 'stats/mp', 1, '20', NULL, NULL, NULL, NULL), + (61, 11, 'inc_atk', 'statsBase/atk', 1, '20', NULL, NULL, NULL, NULL), + (62, 11, 'inc_def', 'statsBase/def', 1, '20', NULL, NULL, NULL, NULL), + (63, 11, 'inc_hp', 'statsBase/hp', 1, '20', NULL, NULL, NULL, NULL), + (64, 11, 'inc_mp', 'statsBase/mp', 1, '20', NULL, NULL, NULL, NULL), + (65, 12, 'inc_atk', 'stats/atk', 1, '50', NULL, NULL, NULL, NULL), + (66, 12, 'inc_def', 'stats/def', 1, '50', NULL, NULL, NULL, NULL), + (67, 12, 'inc_hp', 'stats/hp', 1, '50', NULL, NULL, NULL, NULL), + (68, 12, 'inc_mp', 'stats/mp', 1, '50', NULL, NULL, NULL, NULL), + (69, 12, 'inc_atk', 'statsBase/atk', 1, '50', NULL, NULL, NULL, NULL), + (70, 12, 'inc_def', 'statsBase/def', 1, '50', NULL, NULL, NULL, NULL), + (71, 12, 'inc_hp', 'statsBase/hp', 1, '50', NULL, NULL, NULL, NULL), + (72, 12, 'inc_mp', 'statsBase/mp', 1, '50', NULL, NULL, NULL, NULL), + (73, 14, 'inc_atk', 'stats/atk', 1, '10', NULL, NULL, NULL, NULL), + (74, 14, 'inc_def', 'stats/def', 1, '10', NULL, NULL, NULL, NULL), + (75, 14, 'inc_hp', 'stats/hp', 1, '10', NULL, NULL, NULL, NULL), + (76, 14, 'inc_mp', 'stats/mp', 1, '10', NULL, NULL, NULL, NULL), + (77, 14, 'inc_atk', 'statsBase/atk', 1, '10', NULL, NULL, NULL, NULL), + (78, 14, 'inc_def', 'statsBase/def', 1, '10', NULL, NULL, NULL, NULL), + (79, 14, 'inc_hp', 'statsBase/hp', 1, '10', NULL, NULL, NULL, NULL), + (80, 14, 'inc_mp', 'statsBase/mp', 1, '10', NULL, NULL, NULL, NULL), + (81, 15, 'inc_atk', 'stats/atk', 1, '20', NULL, NULL, NULL, NULL), + (82, 15, 'inc_def', 'stats/def', 1, '20', NULL, NULL, NULL, NULL), + (83, 15, 'inc_hp', 'stats/hp', 1, '20', NULL, NULL, NULL, NULL), + (84, 15, 'inc_mp', 'stats/mp', 1, '20', NULL, NULL, NULL, NULL), + (85, 15, 'inc_atk', 'statsBase/atk', 1, '20', NULL, NULL, NULL, NULL), + (86, 15, 'inc_def', 'statsBase/def', 1, '20', NULL, NULL, NULL, NULL), + (87, 15, 'inc_hp', 'statsBase/hp', 1, '20', NULL, NULL, NULL, NULL), + (88, 15, 'inc_mp', 'statsBase/mp', 1, '20', NULL, NULL, NULL, NULL), + (89, 16, 'inc_atk', 'stats/atk', 1, '50', NULL, NULL, NULL, NULL), + (90, 16, 'inc_def', 'stats/def', 1, '50', NULL, NULL, NULL, NULL), + (91, 16, 'inc_hp', 'stats/hp', 1, '50', NULL, NULL, NULL, NULL), + (92, 16, 'inc_mp', 'stats/mp', 1, '50', NULL, NULL, NULL, NULL), + (93, 16, 'inc_atk', 'statsBase/atk', 1, '50', NULL, NULL, NULL, NULL), + (94, 16, 'inc_def', 'statsBase/def', 1, '50', NULL, NULL, NULL, NULL), + (95, 16, 'inc_hp', 'statsBase/hp', 1, '50', NULL, NULL, NULL, NULL), + (96, 16, 'inc_mp', 'statsBase/mp', 1, '50', NULL, NULL, NULL, NULL), + (97, 18, 'inc_atk', 'stats/atk', 1, '10', NULL, NULL, NULL, NULL), + (98, 18, 'inc_def', 'stats/def', 1, '10', NULL, NULL, NULL, NULL), + (99, 18, 'inc_hp', 'stats/hp', 1, '10', NULL, NULL, NULL, NULL), + (100, 18, 'inc_mp', 'stats/mp', 1, '10', NULL, NULL, NULL, NULL), + (101, 18, 'inc_atk', 'statsBase/atk', 1, '10', NULL, NULL, NULL, NULL), + (102, 18, 'inc_def', 'statsBase/def', 1, '10', NULL, NULL, NULL, NULL), + (103, 18, 'inc_hp', 'statsBase/hp', 1, '10', NULL, NULL, NULL, NULL), + (104, 18, 'inc_mp', 'statsBase/mp', 1, '10', NULL, NULL, NULL, NULL), + (105, 19, 'inc_atk', 'stats/atk', 1, '20', NULL, NULL, NULL, NULL), + (106, 19, 'inc_def', 'stats/def', 1, '20', NULL, NULL, NULL, NULL), + (107, 19, 'inc_hp', 'stats/hp', 1, '20', NULL, NULL, NULL, NULL), + (108, 19, 'inc_mp', 'stats/mp', 1, '20', NULL, NULL, NULL, NULL), + (109, 19, 'inc_atk', 'statsBase/atk', 1, '20', NULL, NULL, NULL, NULL), + (110, 19, 'inc_def', 'statsBase/def', 1, '20', NULL, NULL, NULL, NULL), + (111, 19, 'inc_hp', 'statsBase/hp', 1, '20', NULL, NULL, NULL, NULL), + (112, 19, 'inc_mp', 'statsBase/mp', 1, '20', NULL, NULL, NULL, NULL), + (113, 20, 'inc_atk', 'stats/atk', 1, '50', NULL, NULL, NULL, NULL), + (114, 20, 'inc_def', 'stats/def', 1, '50', NULL, NULL, NULL, NULL), + (115, 20, 'inc_hp', 'stats/hp', 1, '50', NULL, NULL, NULL, NULL), + (116, 20, 'inc_mp', 'stats/mp', 1, '50', NULL, NULL, NULL, NULL), + (117, 20, 'inc_atk', 'statsBase/atk', 1, '50', NULL, NULL, NULL, NULL), + (118, 20, 'inc_def', 'statsBase/def', 1, '50', NULL, NULL, NULL, NULL), + (119, 20, 'inc_hp', 'statsBase/hp', 1, '50', NULL, NULL, NULL, NULL), + (120, 20, 'inc_mp', 'statsBase/mp', 1, '50', NULL, NULL, NULL, NULL); +/*!40000 ALTER TABLE `skills_levels_modifiers` ENABLE KEYS */; + +-- Dumping structure for table skills_levels_modifiers_conditions +CREATE TABLE IF NOT EXISTS `skills_levels_modifiers_conditions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `levels_modifier_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` varchar(50) CHARACTER SET utf32 COLLATE utf32_unicode_ci NOT NULL COMMENT 'eq,ne,lt,gt,le,ge', + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `levels_modifier_id` (`levels_modifier_id`) USING BTREE, + CONSTRAINT `FK_skills_levels_modifiers_conditions_skills_levels_modifiers` FOREIGN KEY (`levels_modifier_id`) REFERENCES `skills_levels_modifiers` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf32 COLLATE=utf32_unicode_ci; + +-- Dumping data for table skills_levels_modifiers_conditions: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_levels_modifiers_conditions` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_levels_modifiers_conditions` ENABLE KEYS */; + +-- Dumping structure for table skills_levels_set +CREATE TABLE IF NOT EXISTS `skills_levels_set` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `autoFillRanges` int unsigned NOT NULL DEFAULT '0', + `autoFillExperienceMultiplier` int unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_levels_set: ~5 rows (approximately) +/*!40000 ALTER TABLE `skills_levels_set` DISABLE KEYS */; +INSERT INTO `skills_levels_set` (`id`, `autoFillRanges`, `autoFillExperienceMultiplier`) VALUES + (1, 1, NULL), + (2, 1, NULL), + (3, 1, NULL), + (4, 1, NULL), + (5, 1, NULL); +/*!40000 ALTER TABLE `skills_levels_set` ENABLE KEYS */; + +-- Dumping structure for table skills_owners_class_path +CREATE TABLE IF NOT EXISTS `skills_owners_class_path` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `class_path_id` int unsigned NOT NULL, + `owner_id` int unsigned NOT NULL, + `currentLevel` bigint unsigned NOT NULL DEFAULT '0', + `currentExp` bigint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `level_set_id` (`class_path_id`) USING BTREE, + CONSTRAINT `FK_skills_owners_class_path_skills_class_path` FOREIGN KEY (`class_path_id`) REFERENCES `skills_class_path` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_owners_class_path: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_owners_class_path` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_owners_class_path` ENABLE KEYS */; + +-- Dumping structure for table skills_skill +CREATE TABLE IF NOT EXISTS `skills_skill` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'B: 1, ATK: 2, EFCT: 3, PHYS-ATK: 4, PHYS-EFCT: 5', + `autoValidation` int NOT NULL, + `skillDelay` int NOT NULL, + `castTime` int NOT NULL, + `usesLimit` int NOT NULL DEFAULT '0', + `range` int NOT NULL, + `rangeAutomaticValidation` int NOT NULL, + `rangePropertyX` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Property path', + `rangePropertyY` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Property path', + `rangeTargetPropertyX` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Target property path', + `rangeTargetPropertyY` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Target property path', + `allowSelfTarget` int NOT NULL, + `criticalChance` int DEFAULT NULL, + `criticalMultiplier` int DEFAULT NULL, + `criticalFixedValue` int DEFAULT NULL, + `customData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT 'Any custom data, recommended JSON format.', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `key` (`key`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_skill: ~4 rows (approximately) +/*!40000 ALTER TABLE `skills_skill` DISABLE KEYS */; +INSERT INTO `skills_skill` (`id`, `key`, `type`, `autoValidation`, `skillDelay`, `castTime`, `usesLimit`, `range`, `rangeAutomaticValidation`, `rangePropertyX`, `rangePropertyY`, `rangeTargetPropertyX`, `rangeTargetPropertyY`, `allowSelfTarget`, `criticalChance`, `criticalMultiplier`, `criticalFixedValue`, `customData`) VALUES + (1, 'attackBullet', '4', 0, 1000, 0, 0, 250, 1, 'state/x', 'state/y', NULL, NULL, 0, 10, 2, 0, NULL), + (2, 'attackShort', '2', 0, 600, 0, 0, 50, 1, 'state/x', 'state/y', NULL, NULL, 0, 10, 2, 0, NULL), + (3, 'fireball', '4', 0, 1500, 2000, 0, 280, 1, 'state/x', 'state/y', NULL, NULL, 0, 10, 2, 0, NULL), + (4, 'heal', '3', 0, 1500, 2000, 0, 100, 1, 'state/x', 'state/y', NULL, NULL, 1, 0, 1, 0, NULL); +/*!40000 ALTER TABLE `skills_skill` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_animations +CREATE TABLE IF NOT EXISTS `skills_skill_animations` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Name conventions [key] + _atk, _cast, _bullet, _hit or _death.', + `classKey` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `animationData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `skill_id_key` (`skill_id`,`key`) USING BTREE, + KEY `id` (`id`) USING BTREE, + KEY `key` (`key`) USING BTREE, + KEY `skill_id` (`skill_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_animations_skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_skill_animations: ~4 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_animations` DISABLE KEYS */; +INSERT INTO `skills_skill_animations` (`id`, `skill_id`, `key`, `classKey`, `animationData`) VALUES + (1, 3, 'bullet', NULL, '{"enabled":true,"type":"spritesheet","img":"fireball_bullet","frameWidth":64,"frameHeight":64,"start":0,"end":3,"repeat":-1,"rate":1,"dir":3}'), + (2, 3, 'cast', NULL, '{"enabled":true,"type":"spritesheet","img":"fireball_cast","frameWidth":64,"frameHeight":70,"start":0,"end":3,"repeat":-1,"destroyTime":2000,"depthByPlayer":"above"}'), + (3, 4, 'cast', NULL, '{"enabled":true,"type":"spritesheet","img":"heal_cast","frameWidth":64,"frameHeight":70,"start":0,"end":3,"repeat":-1,"destroyTime":2000}'), + (6, 4, 'hit', NULL, '{"enabled":true,"type":"spritesheet","img":"heal_hit","frameWidth":64,"frameHeight":70,"start":0,"end":4,"repeat":0,"depthByPlayer":"above"}'); +/*!40000 ALTER TABLE `skills_skill_animations` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_attack +CREATE TABLE IF NOT EXISTS `skills_skill_attack` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `affectedProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `allowEffectBelowZero` int unsigned NOT NULL DEFAULT '0', + `hitDamage` int unsigned NOT NULL, + `applyDirectDamage` int unsigned NOT NULL DEFAULT '0', + `attackProperties` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `defenseProperties` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `aimProperties` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `dodgeProperties` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `dodgeFullEnabled` int NOT NULL DEFAULT '1', + `dodgeOverAimSuccess` int NOT NULL DEFAULT '2', + `damageAffected` int NOT NULL DEFAULT '0', + `criticalAffected` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `skill_id` (`skill_id`), + CONSTRAINT `FK__skills_skill_attack` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_skill_attack: ~3 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_attack` DISABLE KEYS */; +INSERT INTO `skills_skill_attack` (`id`, `skill_id`, `affectedProperty`, `allowEffectBelowZero`, `hitDamage`, `applyDirectDamage`, `attackProperties`, `defenseProperties`, `aimProperties`, `dodgeProperties`, `dodgeFullEnabled`, `dodgeOverAimSuccess`, `damageAffected`, `criticalAffected`) VALUES + (1, 1, 'stats/hp', 0, 3, 0, 'stats/atk,stats/stamina,stats/speed', 'stats/def,stats/stamina,stats/speed', 'stats/aim', 'stats/dodge', 1, 2, 0, 0), + (2, 2, 'stats/hp', 0, 5, 0, 'stats/atk,stats/stamina,stats/speed', 'stats/def,stats/stamina,stats/speed', 'stats/aim', 'stats/dodge', 1, 2, 0, 0), + (3, 3, 'stats/hp', 0, 7, 0, 'stats/atk,stats/stamina,stats/speed', 'stats/def,stats/stamina,stats/speed', 'stats/aim', 'stats/dodge', 1, 2, 0, 0); +/*!40000 ALTER TABLE `skills_skill_attack` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_group_relation +CREATE TABLE IF NOT EXISTS `skills_skill_group_relation` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `group_id` int unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `group_id` (`group_id`), + KEY `skill_id` (`skill_id`), + CONSTRAINT `FK__skills_groups` FOREIGN KEY (`group_id`) REFERENCES `skills_groups` (`id`) ON UPDATE CASCADE, + CONSTRAINT `FK__skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_skill_group_relation: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_group_relation` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_skill_group_relation` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_owner_conditions +CREATE TABLE IF NOT EXISTS `skills_skill_owner_conditions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `conditional` varchar(50) CHARACTER SET utf32 COLLATE utf32_unicode_ci NOT NULL COMMENT 'eq,ne,lt,gt,le,ge', + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `skill_id` (`skill_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_owner_conditions_skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf32 COLLATE=utf32_unicode_ci; + +-- Dumping data for table skills_skill_owner_conditions: ~1 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_owner_conditions` DISABLE KEYS */; +INSERT INTO `skills_skill_owner_conditions` (`id`, `skill_id`, `key`, `property_key`, `conditional`, `value`) VALUES + (1, 3, 'available_mp', 'stats/mp', 'ge', '5'); +/*!40000 ALTER TABLE `skills_skill_owner_conditions` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_owner_effects +CREATE TABLE IF NOT EXISTS `skills_skill_owner_effects` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` int NOT NULL, + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `maxValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `skill_id` (`skill_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_owner_effects_skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Modifiers table.'; + +-- Dumping data for table skills_skill_owner_effects: ~1 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_owner_effects` DISABLE KEYS */; +INSERT INTO `skills_skill_owner_effects` (`id`, `skill_id`, `key`, `property_key`, `operation`, `value`, `minValue`, `maxValue`, `minProperty`, `maxProperty`) VALUES + (2, 3, 'dec_mp', 'stats/mp', 2, '5', '0', '', NULL, NULL); +/*!40000 ALTER TABLE `skills_skill_owner_effects` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_owner_effects_conditions +CREATE TABLE IF NOT EXISTS `skills_skill_owner_effects_conditions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_owner_effect_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` varchar(50) CHARACTER SET utf32 COLLATE utf32_unicode_ci NOT NULL COMMENT 'eq,ne,lt,gt,le,ge', + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `skill_owner_effect_id` (`skill_owner_effect_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_owner_effects_conditions_skill_owner_effects` FOREIGN KEY (`skill_owner_effect_id`) REFERENCES `skills_skill_owner_effects` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf32 COLLATE=utf32_unicode_ci; + +-- Dumping data for table skills_skill_owner_effects_conditions: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_owner_effects_conditions` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_skill_owner_effects_conditions` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_physical_data +CREATE TABLE IF NOT EXISTS `skills_skill_physical_data` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `magnitude` int unsigned NOT NULL, + `objectWidth` int unsigned NOT NULL, + `objectHeight` int unsigned NOT NULL, + `validateTargetOnHit` int unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `attack_skill_id` (`skill_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_physical_data_skills_skill` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table skills_skill_physical_data: ~2 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_physical_data` DISABLE KEYS */; +INSERT INTO `skills_skill_physical_data` (`id`, `skill_id`, `magnitude`, `objectWidth`, `objectHeight`, `validateTargetOnHit`) VALUES + (1, 1, 350, 5, 5, 0), + (2, 3, 550, 5, 5, 0); +/*!40000 ALTER TABLE `skills_skill_physical_data` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_target_effects +CREATE TABLE IF NOT EXISTS `skills_skill_target_effects` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` int unsigned NOT NULL, + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `maxValue` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `minProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `skill_id` (`skill_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_effect_modifiers` FOREIGN KEY (`skill_id`) REFERENCES `skills_skill` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Modifiers table.'; + +-- Dumping data for table skills_skill_target_effects: ~1 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_target_effects` DISABLE KEYS */; +INSERT INTO `skills_skill_target_effects` (`id`, `skill_id`, `key`, `property_key`, `operation`, `value`, `minValue`, `maxValue`, `minProperty`, `maxProperty`) VALUES + (1, 4, 'heal', 'stats/hp', 1, '10', '0', '0', NULL, 'statsBase/hp'); +/*!40000 ALTER TABLE `skills_skill_target_effects` ENABLE KEYS */; + +-- Dumping structure for table skills_skill_target_effects_conditions +CREATE TABLE IF NOT EXISTS `skills_skill_target_effects_conditions` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `skill_target_effect_id` int unsigned NOT NULL, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `property_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `operation` varchar(50) CHARACTER SET utf32 COLLATE utf32_unicode_ci NOT NULL COMMENT 'eq,ne,lt,gt,le,ge', + `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `skill_target_effect_id` (`skill_target_effect_id`) USING BTREE, + CONSTRAINT `FK_skills_skill_target_effects_conditions_skill_target_effects` FOREIGN KEY (`skill_target_effect_id`) REFERENCES `skills_skill_target_effects` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf32 COLLATE=utf32_unicode_ci; + +-- Dumping data for table skills_skill_target_effects_conditions: ~0 rows (approximately) +/*!40000 ALTER TABLE `skills_skill_target_effects_conditions` DISABLE KEYS */; +/*!40000 ALTER TABLE `skills_skill_target_effects_conditions` ENABLE KEYS */; + +-- Dumping structure for table stats +CREATE TABLE IF NOT EXISTS `stats` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `base_value` int unsigned NOT NULL, + `customData` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `key` (`key`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; + +-- Dumping data for table stats: ~10 rows (approximately) +/*!40000 ALTER TABLE `stats` DISABLE KEYS */; +INSERT INTO `stats` (`id`, `key`, `label`, `description`, `base_value`, `customData`) VALUES + (1, 'hp', 'HP', 'Player life points', 100, '{"showBase":true}'), + (2, 'mp', 'MP', 'Player magic points', 100, '{"showBase":true}'), + (3, 'atk', 'Atk', 'Player attack points', 100, NULL), + (4, 'def', 'Def', 'Player defense points', 100, NULL), + (5, 'dodge', 'Dodge', 'Player dodge points', 100, NULL), + (6, 'speed', 'Speed', 'Player speed point', 100, NULL), + (7, 'aim', 'Aim', 'Player aim points', 100, NULL), + (8, 'stamina', 'Stamina', 'Player stamina points', 100, '{"showBase":true}'), + (9, 'mgk-atk', 'Magic Atk', 'Player magic attack', 100, NULL), + (10, 'mgk-def', 'Magic Def', 'Player magic defense', 100, NULL); +/*!40000 ALTER TABLE `stats` ENABLE KEYS */; + +-- Dumping structure for table users +CREATE TABLE IF NOT EXISTS `users` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `email` varchar(255) NOT NULL, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `role_id` int unsigned NOT NULL, + `status` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`), + UNIQUE KEY `username` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- Dumping data for table users: ~0 rows (approximately) +/*!40000 ALTER TABLE `users` DISABLE KEYS */; +/*!40000 ALTER TABLE `users` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 18e0ea27b..611e4dac0 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -58,6 +58,9 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/clan/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/controls/allowPrimaryTouch', '1', @boolean_id); +INSERT INTO `config` VALUES (NULL, 'server', 'rewards/actions/interactionsDistance', '140', @float_id); +INSERT INTO `config` VALUES (NULL, 'server', 'rewards/actions/disappearTime', '1800000', @float_id); +INSERT INTO `config` VALUES (NULL, 'client', 'rewards/titles/rewardMessage', 'You obtained %dropQuantity %itemLabel', @string_id); # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); @@ -158,7 +161,7 @@ CREATE TABLE `rewards_modifiers` ( `maxProperty` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), KEY `modifier_id` (`key`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci COMMENT='Reward Modifiers table.'; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci; CREATE TABLE `rewards` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -191,7 +194,6 @@ CREATE TABLE `objects_items_rewards_animations` ( CONSTRAINT `FK_objects_items_rewards_animations_rewards` FOREIGN KEY (`reward_id`) REFERENCES `rewards` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION ) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; - INSERT INTO `rewards` VALUES (1, 7, 2, null, 10, 100, 1, 0, 0, 1), (2, 6, 2, null, 10, 100, 3, 0, 0, 1); @@ -200,11 +202,6 @@ INSERT INTO `objects_items_rewards_animations` VALUES (1, 2, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'), (2, 1, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'); -# Drop Reward Interaction Distance Config -INSERT INTO `config` (scope, path, value, type) VALUES ('server', 'rewards/actions/interactionsDistance', '140', 2); -INSERT INTO `config` (scope, path, value, type) VALUES ('server', 'rewards/disappearTime', '1800000', 2); -INSERT INTO `config` (scope, path, value, type) VALUES ('client', 'rewards/titles/rewardMessage', 'You obtained %dropQuantity %itemLabel', 1); - ####################################################################################################################### SET FOREIGN_KEY_CHECKS = 1; From aadd61b0a9a5fd1eae2f5451e91a592f8866e1cb Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 31 Mar 2023 20:35:27 +0200 Subject: [PATCH 34/82] - Reldens - v4.0.0 - Post rewards merge fixes. --- lib/rewards/server/models/reward.js | 9 +++------ lib/rewards/server/subscribers/rewards-subscriber.js | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/rewards/server/models/reward.js b/lib/rewards/server/models/reward.js index 4506c8d5c..2726a55b4 100644 --- a/lib/rewards/server/models/reward.js +++ b/lib/rewards/server/models/reward.js @@ -44,20 +44,17 @@ class Reward if(this.isUnique && this.wasGiven){ return false; } - if(!this.isItemType() && !this.isModifierType() && !this.hasExperienceSet()){ - return false; - } - return true; + return !(!this.isItemType() && !this.isModifierType() && !this.hasExperienceSet()); } isItemType() { - return 0 !== Number(this.itemId); + return this.itemId; } isModifierType() { - return 0 !== Number(this.modifierId); + return this.modifierId; } hasExperienceSet() diff --git a/lib/rewards/server/subscribers/rewards-subscriber.js b/lib/rewards/server/subscribers/rewards-subscriber.js index a9b58b536..702ac6627 100644 --- a/lib/rewards/server/subscribers/rewards-subscriber.js +++ b/lib/rewards/server/subscribers/rewards-subscriber.js @@ -29,14 +29,14 @@ class RewardsSubscriber } let itemRewards = []; for(let winningReward of winningRewards){ - let giveRewardResults = await this.giveReward(winningReward, playerSchema, async (item) => { + let giveRewardErrors = await this.giveReward(winningReward, playerSchema, async (item) => { itemRewards.push({item, reward: winningReward}); }); - if(0 === giveRewardResults.length){ + if(0 < giveRewardErrors.length){ Logger.error( 'There was an error on the reward ' + winningReward.id +' given to the player ' + playerSchema.id, winningReward, - giveRewardResults + giveRewardErrors ); } } From 81b33053f3b53a91dbcf9ce217148fbe7fa0eec6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sat, 1 Apr 2023 20:11:56 +0200 Subject: [PATCH 35/82] - Reldens - v4.0.0 - Player disconnects team leave fix. --- lib/game/server/login-manager.js | 8 ++++---- lib/teams/client/clan-message-handler.js | 13 +++++++++---- lib/teams/client/team-target-box-enricher.js | 4 ++-- lib/teams/server/message-actions/team-leave.js | 17 ++++++++++------- lib/teams/server/plugin.js | 4 ++++ lib/teams/server/team-message-actions.js | 4 ++-- lib/teams/server/team-updates-handler.js | 4 ++-- lib/users/server/manager.js | 2 +- lib/users/server/plugin.js | 2 +- 9 files changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/game/server/login-manager.js b/lib/game/server/login-manager.js index bdfd974ee..7a1fc9a4e 100644 --- a/lib/game/server/login-manager.js +++ b/lib/game/server/login-manager.js @@ -80,7 +80,7 @@ class LoginManager { // check if the passwords match: if(!this.passwordManager.validatePassword(userData.password, user.password)){ - let result = {error: 'User already exists or invalid user data.'}; + let result = {error: 'Invalid user data.'}; this.events.emitSync('reldens.loginInvalidPassword', this, user, userData, result); return result; } @@ -198,10 +198,10 @@ class LoginManager state: initialState }; await this.events.emit('reldens.createNewPlayerBefore', loginData, playerData, this); - let isUnavailable = await this.usersManager.isNameAvailable(playerData.name); - if(isUnavailable){ + let isTaken = await this.usersManager.isNameAvailable(playerData.name); + if(!isTaken){ let result = {error: true, message: 'The player name is not available, please choose another name.'}; - await this.events.emit('reldens.playerNewNameUnavailable', this, loginData, isUnavailable, result); + await this.events.emit('reldens.playerNewNameUnavailable', this, loginData, isTaken, result); return result; } try { diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index ede4cf12e..9443aeac7 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -89,7 +89,7 @@ class ClanMessageHandler showClanRequest() { - let clanUi = this.createClanUi(); + this.createClanUi(); this.roomEvents.initUi({ id: TeamsConst.CLAN_KEY, title: this.gameManager.config.getWithoutLogs( @@ -107,7 +107,7 @@ class ClanMessageHandler showClanBox() { - let clanUi = this.createClanUi(); + this.createClanUi(); let title = this.gameManager.config.getWithoutLogs( 'client/clan/labels/clanTitle', TeamsConst.LABELS.CLAN.CLAN_TITLE @@ -123,7 +123,12 @@ class ClanMessageHandler this.roomEvents.uiSetTitle(uiBox, {title}); this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); let players = sc.get(this.message, 'players', false); - this.uiScene.currentTeam = players; + this.uiScene.currentClan = { + id: this.message.id, + name: this.message.clanName, + leader: this.message.leaderName, + players + }; this.updateClanBox(players, container); } @@ -249,7 +254,7 @@ class ClanMessageHandler let leaveButton = this.gameManager.gameDom.getElement('.leave-'+this.message.id); leaveButton?.addEventListener('click', () => { this.gameManager.activeRoomEvents.room.send('*', { - act: TeamsConst.ACTIONS.TEAM_LEAVE, + act: TeamsConst.ACTIONS.CLAN_LEAVE, id: this.message.id }); }); diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/team-target-box-enricher.js index 3da273e42..3f048bc65 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/team-target-box-enricher.js @@ -14,13 +14,13 @@ class TeamTargetBoxEnricher static appendClanInviteButton(gameManager, target, previousTarget, targetName) { let currentPlayer = gameManager.getCurrentPlayer(); - if(!currentPlayer.clan){ + if(!currentPlayer.currentClan){ return false; } if(!this.targetIsValidPlayer(target, currentPlayer)){ return false; } - if(currentPlayer.clan.members[target.id]){ + if(currentPlayer.currentClan.players[target.id]){ return false; } return this.appendInviteButton('clan', target, gameManager, targetName); diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 0153451ea..3e22ea8f2 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -11,13 +11,18 @@ const { Logger, sc } = require('@reldens/utils'); class TeamLeave { - static async execute(client, data, room, playerSchema, teamsPlugin) + static async fromMessage(data, room, playerSchema, teamsPlugin) { - await teamsPlugin.events.emit('reldens.teamLeave', {client, data, room, playerSchema, teamsPlugin}); + await teamsPlugin.events.emit('reldens.teamLeave', {data, room, playerSchema, teamsPlugin}); if(TeamsConst.ACTIONS.TEAM_REMOVE === data.act && data.id !== playerSchema.sessionId){ Logger.error('Team remove failed, player "'+playerSchema.playerName+'" not allowed.'); return false; } + return await this.execute(room, playerSchema, teamsPlugin, sc.get(data, 'remove', playerSchema.id)); + } + + static async execute(room, playerSchema, teamsPlugin, singleRemoveId) + { let teamId = playerSchema.currentTeam; if(!teamId){ return false; @@ -29,7 +34,6 @@ class TeamLeave return false; } let playerIds = Object.keys(currentTeam.players); - let singleRemoveId = sc.get(data, 'remove', playerSchema.id); let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ let sendUpdate = { @@ -40,8 +44,7 @@ class TeamLeave await teamsPlugin.events.emit('reldens.teamLeaveBeforeSendUpdate', { playerId, sendUpdate, - client, - data, + singleRemoveId, room, playerSchema, teamsPlugin @@ -50,7 +53,7 @@ class TeamLeave currentTeam.leave(currentTeam.players[playerId]); } if(1 >= Object.keys(currentTeam.players).length){ - let event = {client, data, room, playerSchema, teamsPlugin, continueDisband: true}; + let event = {singleRemoveId, room, playerSchema, teamsPlugin, continueDisband: true}; await teamsPlugin.events.emit('reldens.beforeTeamDisband', event); if(!event.continueDisband){ return false; @@ -58,7 +61,7 @@ class TeamLeave delete teamsPlugin.teams[teamId]; return true; } - let event = {client, data, room, playerSchema, teamsPlugin, continueLeave: true}; + let event = {singleRemoveId, room, playerSchema, teamsPlugin, continueLeave: true}; await teamsPlugin.events.emit('reldens.beforeTeamDisband', event); if(!event.continueLeave){ return false; diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 59ca5f8fd..96d15b9ba 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -12,6 +12,7 @@ const { CreatePlayerTeamHandler } = require('./event-handlers/create-player-team const { StatsUpdateHandler } = require('./event-handlers/stats-update-handler'); const { EndPlayerHitChangePointTeamHandler } = require('./event-handlers/end-player-hit-change-point-team-handler'); const { Logger, sc } = require('@reldens/utils'); +const {TeamLeave} = require("./message-actions/team-leave"); class TeamsPlugin extends PluginInterface { @@ -44,6 +45,9 @@ class TeamsPlugin extends PluginInterface this.events.on('reldens.endPlayerHitChangePoint', async (event) => { await EndPlayerHitChangePointTeamHandler.savePlayerTeam(event.playerSchema, this); }); + this.events.on('reldens.removeAllPlayerReferencesBefore', async(event) => { + return await TeamLeave.execute(event.room, event.playerSchema, this); + }); } } diff --git a/lib/teams/server/team-message-actions.js b/lib/teams/server/team-message-actions.js index e8f604e41..dd8d59df6 100644 --- a/lib/teams/server/team-message-actions.js +++ b/lib/teams/server/team-message-actions.js @@ -33,10 +33,10 @@ class TeamMessageActions return await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.TEAM_LEAVE === data.act){ - return await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); + return await TeamLeave.fromMessage(data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.TEAM_REMOVE === data.act){ - return await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); + return await TeamLeave.fromMessage(data, room, playerSchema, this.teamsPlugin); } } diff --git a/lib/teams/server/team-updates-handler.js b/lib/teams/server/team-updates-handler.js index cb0bb1f80..e8a96277d 100644 --- a/lib/teams/server/team-updates-handler.js +++ b/lib/teams/server/team-updates-handler.js @@ -13,11 +13,11 @@ class TeamUpdatesHandler static updateTeamPlayers(team) { let clientsKeys = Object.keys(team.clients); - if (0 === clientsKeys.length) { + if(0 === clientsKeys.length){ return false; } let playersList = PlayersDataMapper.fetchPlayersData(team); - if (0 === Object.keys(playersList).length) { + if(0 === Object.keys(playersList).length){ return false; } for(let i of clientsKeys){ diff --git a/lib/users/server/manager.js b/lib/users/server/manager.js index 8dbe2c88f..57e157f17 100644 --- a/lib/users/server/manager.js +++ b/lib/users/server/manager.js @@ -56,7 +56,7 @@ class UsersManager async isNameAvailable(playerName) { - return !!(await this.dataServer.getEntity('players').loadOneBy('name', playerName)); + return await this.dataServer.getEntity('players').loadOneBy('name', playerName)?.name; } async createPlayer(playerData) diff --git a/lib/users/server/plugin.js b/lib/users/server/plugin.js index 8a96419c2..41b00b7b6 100644 --- a/lib/users/server/plugin.js +++ b/lib/users/server/plugin.js @@ -26,7 +26,7 @@ class UsersPlugin extends PluginInterface if(!this.dataServer){ Logger.error('DataServer undefined in UsersPlugin.'); } - // @TODO - BETA - Move LifeBar to it's own package. + // @TODO - BETA - Move LifeBar to it's own package, convert into PropertyUpdater and make generic. this.lifeBarConfig = false; this.lifeProp = false; this.events.on('reldens.serverReady', async (event) => { From b5958538ed71c4ec1956df67600f8b493a1396b7 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 2 Apr 2023 20:40:14 +0200 Subject: [PATCH 36/82] - Reldens - v4.0.0 - Minimap camera fix. --- lib/game/client/minimap.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/game/client/minimap.js b/lib/game/client/minimap.js index ab5b569f0..1b35faa40 100644 --- a/lib/game/client/minimap.js +++ b/lib/game/client/minimap.js @@ -18,6 +18,7 @@ class Minimap createMap(scene, playerSprite) { + // @TODO - BETA - Improve camera. this.autoWidth = scene.map.widthInPixels / sc.get(this.config, 'mapWidthDivisor', 1); this.camWidth = sc.get(this.config, 'fixedWidth', this.autoWidth); this.autoHeight = scene.map.heightInPixels / sc.get(this.config, 'mapHeightDivisor', 1); @@ -30,9 +31,18 @@ class Minimap .setName('minimap') .setBackgroundColor(this.camBackgroundColor) .setZoom(this.camZoom) - .startFollow(playerSprite) + .startFollow( + playerSprite, + sc.get(this.config, 'mapCameraRoundPixels', true), + sc.get(this.config, 'mapCameraLerpX', 1), + sc.get(this.config, 'mapCameraLerpY', 1) + ) .setRoundPixels(true) - .setVisible(false); + .setVisible(false) + .setOrigin( + sc.get(this.config, 'mapCameraOriginX', 0.18), + sc.get(this.config, 'mapCameraOriginY', 0.18) + ); this.roundMap = sc.get(this.config, 'roundMap', false); if(this.roundMap){ // @NOTE: because of the camara zoom the circle size append to the preload scene is different from the map From ec07adc72245e25b661432965ad47de0ed636fff Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 3 Apr 2023 20:49:05 +0200 Subject: [PATCH 37/82] - Reldens - v4.0.0 - Clan remove, disband, disconnect. --- lib/teams/client/clan-message-handler.js | 5 +- lib/teams/client/plugin.js | 6 +- ...box-enricher.js => target-box-enricher.js} | 6 +- lib/teams/server/clan-message-actions.js | 15 ++-- .../create-player-clan-handler.js | 4 + .../server/message-actions/clan-create.js | 15 ++-- .../server/message-actions/clan-disconnect.js | 63 ++++++++++++++++ lib/teams/server/message-actions/clan-join.js | 72 ++++++++++++++++++ .../server/message-actions/clan-leave.js | 75 +++++++++++++++++++ lib/teams/server/message-actions/team-join.js | 2 +- .../server/message-actions/team-leave.js | 4 +- .../server/message-actions/try-clan-invite.js | 40 ++++++++++ .../server/message-actions/try-team-start.js | 3 +- 13 files changed, 281 insertions(+), 29 deletions(-) rename lib/teams/client/{team-target-box-enricher.js => target-box-enricher.js} (95%) create mode 100644 lib/teams/server/message-actions/clan-disconnect.js create mode 100644 lib/teams/server/message-actions/clan-join.js create mode 100644 lib/teams/server/message-actions/clan-leave.js create mode 100644 lib/teams/server/message-actions/try-clan-invite.js diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 9443aeac7..817062bc8 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -253,10 +253,11 @@ class ClanMessageHandler { let leaveButton = this.gameManager.gameDom.getElement('.leave-'+this.message.id); leaveButton?.addEventListener('click', () => { - this.gameManager.activeRoomEvents.room.send('*', { + let sendData = { act: TeamsConst.ACTIONS.CLAN_LEAVE, id: this.message.id - }); + }; + this.gameManager.activeRoomEvents.room.send('*', sendData); }); } diff --git a/lib/teams/client/plugin.js b/lib/teams/client/plugin.js index f26e8624a..e01845426 100644 --- a/lib/teams/client/plugin.js +++ b/lib/teams/client/plugin.js @@ -5,7 +5,7 @@ */ const { PluginInterface } = require('../../features/plugin-interface'); -const { TeamTargetBoxEnricher } = require('./team-target-box-enricher'); +const { TargetBoxEnricher } = require('./target-box-enricher'); const { TeamMessageListener } = require('./team-message-listener'); const { ClanMessageListener } = require('./clan-message-listener'); const { MessageProcessor } = require('./messages-processor'); @@ -41,8 +41,8 @@ class TeamsPlugin extends PluginInterface TemplatesHandler.preloadTemplates(preloadScene); }); this.events.on('reldens.gameEngineShowTarget', (gameEngine, target, previousTarget, targetName) => { - TeamTargetBoxEnricher.appendClanInviteButton(this.gameManager, target, previousTarget, targetName); - TeamTargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); + TargetBoxEnricher.appendClanInviteButton(this.gameManager, target, previousTarget, targetName); + TargetBoxEnricher.appendTeamInviteButton(this.gameManager, target, previousTarget, targetName); }); // @TODO - BETA - Standardize, listeners on config or added by events like: // this.events.on('reldens.activateRoom', (room) => { diff --git a/lib/teams/client/team-target-box-enricher.js b/lib/teams/client/target-box-enricher.js similarity index 95% rename from lib/teams/client/team-target-box-enricher.js rename to lib/teams/client/target-box-enricher.js index 3f048bc65..c5bea978f 100644 --- a/lib/teams/client/team-target-box-enricher.js +++ b/lib/teams/client/target-box-enricher.js @@ -1,6 +1,6 @@ /** * - * Reldens - TeamTargetBoxEnricher + * Reldens - TargetBoxEnricher * */ @@ -8,7 +8,7 @@ const { TeamsConst } = require('../constants'); const { GameConst } = require('../../game/constants'); const { Logger, sc } = require('@reldens/utils'); -class TeamTargetBoxEnricher +class TargetBoxEnricher { static appendClanInviteButton(gameManager, target, previousTarget, targetName) @@ -80,4 +80,4 @@ class TeamTargetBoxEnricher } } -module.exports.TeamTargetBoxEnricher = TeamTargetBoxEnricher; +module.exports.TargetBoxEnricher = TargetBoxEnricher; diff --git a/lib/teams/server/clan-message-actions.js b/lib/teams/server/clan-message-actions.js index 849ace5d8..962861c5a 100644 --- a/lib/teams/server/clan-message-actions.js +++ b/lib/teams/server/clan-message-actions.js @@ -5,6 +5,9 @@ */ const { ClanCreate } = require('./message-actions/clan-create'); +const { TryClanInvite } = require('./message-actions/try-clan-invite'); +const { ClanJoin } = require('./message-actions/clan-join'); +const { ClanLeave } = require('./message-actions/clan-leave'); const { TeamsConst } = require('../constants'); const { sc } = require('@reldens/utils'); @@ -25,20 +28,16 @@ class ClanMessageActions return false; } if(TeamsConst.ACTIONS.CLAN_INVITE === data.act){ - // await TryTeamStart.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await TryClanInvite.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.CLAN_CREATE === data.act){ - await ClanCreate.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await ClanCreate.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.CLAN_ACCEPTED === data.act && '1' === data.value){ - // await TeamJoin.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await ClanJoin.execute(client, data, room, playerSchema, this.teamsPlugin); } if(TeamsConst.ACTIONS.CLAN_LEAVE === data.act){ - // await TeamLeave.execute(client, data, room, playerSchema, this.teamsPlugin); - return true; + return await ClanLeave.fromMessage(data, playerSchema, this.teamsPlugin); } } diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 62cd72ea0..76cc91b38 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -33,9 +33,13 @@ class CreatePlayerClanHandler } let clan = await this.loadClan(teamsPlugin, clanMemberModel.clan_id, playerSchema, client, room); if(!clan){ + Logger.error('Loading clan by ID "'+clanMemberModel.clan_id+'" not found.'); return false; } clan.join(playerSchema, client); + if(!playerSchema.privateData){ + playerSchema.privateData = {}; + } playerSchema.privateData.clan = clan; let endEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClanUpdate', endEvent); diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index ac1bfe3bb..45adb2675 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -18,8 +18,7 @@ class ClanCreate let exists = await repository.loadOneBy('name', clanName); if(exists){ Logger.info('Clan already exists with name "'+clanName+'".'); - this.clanCreateSend(client, TeamsConst.VALIDATION.NAME_EXISTS, clanName); - return; + return this.clanCreateSend(client, TeamsConst.VALIDATION.NAME_EXISTS, clanName); } let levelsRepository = teamsPlugin.dataServer.getEntity('clanLevels'); levelsRepository.limit = 1; @@ -27,8 +26,7 @@ class ClanCreate let firstLevel = await levelsRepository.loadOne({}); if(!firstLevel){ Logger.info('Clan creation invalid level "'+clanName+'".'); - this.clanCreateSend(client, TeamsConst.VALIDATION.LEVEL_ISSUE, clanName); - return; + return this.clanCreateSend(client, TeamsConst.VALIDATION.LEVEL_ISSUE, clanName); } levelsRepository.limit = 0; levelsRepository.sortBy = false; @@ -40,8 +38,7 @@ class ClanCreate }); if(!createdClan){ Logger.info('Clan creation error "'+clanName+'".', createdClan); - this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_ERROR, clanName); - return; + return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_ERROR, clanName); } let ownerMember = await teamsPlugin.dataServer.getEntity('clanMembers').create({ clan_id: createdClan.id, @@ -49,10 +46,9 @@ class ClanCreate }); if(!ownerMember){ Logger.info('Clan owner creation error "'+clanName+'".', createdClan, ownerMember); - this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_OWNER_ERROR, clanName); - return; + return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_OWNER_ERROR, clanName); } - this.clanCreateSend(client, TeamsConst.VALIDATION.SUCCESS, clanName, playerSchema.player_id); + return this.clanCreateSend(client, TeamsConst.VALIDATION.SUCCESS, clanName, playerSchema.player_id); } static clanCreateSend(client, result, clanName, ownerId) @@ -68,6 +64,7 @@ class ClanCreate sendData.id = ownerId; } client.send('*', sendData); + return true; } } diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js new file mode 100644 index 000000000..1fc64404a --- /dev/null +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -0,0 +1,63 @@ +/** + * + * Reldens - ClanDisconnect + * + */ + +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { TeamsConst } = require('../../constants'); +const { Logger, sc } = require('@reldens/utils'); + +class ClanDisconnect +{ + + static async execute(playerSchema, teamsPlugin) + { + let clanId = playerSchema?.privateData?.clan?.id; + if(!clanId){ + Logger.warning('Clan ID not found in current player for disconnection.', playerSchema); + return false; + } + let currentClan = teamsPlugin.clans[clanId]; + if(!currentClan){ + Logger.error('Player "'+playerSchema.player_id+'" current clan "'+clanId+'" not found for disconnection.'); + return false; + } + // @NOTE: the way this works is by making the clients leave the clan and then updating the remaining players. + let playerIds = Object.keys(currentClan.players); + let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [playerSchema.player_id]; + for(let playerId of removeByKeys){ + let sendUpdate = { + act: TeamsConst.ACTIONS.CLAN_LEFT, + id: currentClan.ownerClient.id, + listener: TeamsConst.KEY + }; + await teamsPlugin.events.emit('reldens.clanLeaveBeforeSendUpdate', { + playerId, + sendUpdate, + playerSchema, + teamsPlugin + }); + currentClan.clients[playerId].send('*', sendUpdate); + currentClan.leave(currentClan.players[playerId]); + } + if(0 === Object.keys(currentClan.members).length){ + let event = {playerSchema, teamsPlugin, continueDisband: true}; + await teamsPlugin.events.emit('reldens.beforeClanTeamDisband', event); + if(!event.continueDisband){ + return false; + } + delete teamsPlugin.clans[clanId]; + return true; + } + let event = {playerSchema, teamsPlugin, continueLeave: true}; + await teamsPlugin.events.emit('reldens.clanLeaveAfterSendUpdate', event); + if(!event.continueLeave){ + return false; + } + return ClanUpdatesHandler.updateClanPlayers(currentClan); + } + +} + +module.exports.ClanDisconnect = ClanDisconnect; diff --git a/lib/teams/server/message-actions/clan-join.js b/lib/teams/server/message-actions/clan-join.js new file mode 100644 index 000000000..3eaea99c3 --- /dev/null +++ b/lib/teams/server/message-actions/clan-join.js @@ -0,0 +1,72 @@ +/** + * + * Reldens - ClanJoin + * + */ + +const { Clan } = require('../clan'); +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { Logger } = require('@reldens/utils'); + +class ClanJoin +{ + + static async execute(client, data, room, playerSchema, teamsPlugin) + { + if(playerSchema.sessionId === data.id){ + Logger.info('The player is trying to join a clan with himself.', playerSchema.sessionId, data); + return false; + } + if(playerSchema?.privateData?.clan){ + teamsPlugin.clans[playerSchema.player_id]?.leave(playerSchema); + } + let clanOwnerPlayer = room.playerByIdFromState(data.id); + if(!clanOwnerPlayer){ + Logger.error('Player clan owner not found.', clanOwnerPlayer, data); + return false; + } + let clanOwnerClient = room.activePlayers[data.id]; + if(!clanOwnerClient){ + Logger.error('Player clan owner client not found.', clanOwnerClient, data); + return false; + } + let clanProps = { + owner: clanOwnerPlayer, + ownerClient: clanOwnerClient.client, + sharedProperties: room.config.get('client/ui/clans/sharedProperties') + }; + let currentClan = teamsPlugin.clans[clanOwnerClient.id]; + if(!currentClan){ + let beforeCreateEvent = {clanProps, teamsPlugin, continueBeforeCreate: true}; + await teamsPlugin.events.emit('reldens.beforeClanCreate', beforeCreateEvent); + if(!beforeCreateEvent.continueBeforeCreate){ + return false; + } + currentClan = new Clan(clanProps); + } + let eventBeforeJoin = {currentClan, teamsPlugin, continueBeforeJoin: true}; + await teamsPlugin.events.emit('reldens.beforeClanJoin', eventBeforeJoin); + if(!eventBeforeJoin.continueBeforeJoin){ + return false; + } + currentClan.join(playerSchema, client); + if(!clanOwnerPlayer.privateData){ + clanOwnerPlayer.privateData = {}; + } + clanOwnerPlayer.privateData.clan = clanOwnerClient.id; + if(!playerSchema.privateData){ + playerSchema.privateData = {}; + } + playerSchema.privateData.clan = clanOwnerClient.id; + teamsPlugin.clans[clanOwnerClient.id] = currentClan; + let eventBeforeJoinUpdate = {currentClan, teamsPlugin, continueBeforeJoinUpdate: true}; + await teamsPlugin.events.emit('reldens.beforeClanUpdatePlayers', eventBeforeJoinUpdate); + if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ + return false; + } + return ClanUpdatesHandler.updateClanPlayers(currentClan); + } + +} + +module.exports.ClanJoin = ClanJoin; diff --git a/lib/teams/server/message-actions/clan-leave.js b/lib/teams/server/message-actions/clan-leave.js new file mode 100644 index 000000000..495b903c1 --- /dev/null +++ b/lib/teams/server/message-actions/clan-leave.js @@ -0,0 +1,75 @@ +/** + * + * Reldens - ClanLeave + * + */ + +const { ClanUpdatesHandler } = require('../clan-updates-handler'); +const { TeamsConst } = require('../../constants'); +const { Logger, sc } = require('@reldens/utils'); + +class ClanLeave +{ + + static async fromMessage(message, playerSchema, teamsPlugin) + { + await teamsPlugin.events.emit('reldens.clanLeave', {message, playerSchema, teamsPlugin}); + if(TeamsConst.ACTIONS.CLAN_REMOVE === message.act && message.id !== playerSchema.sessionId){ + Logger.error('Clan remove failed, player "'+playerSchema.playerName+'" not allowed.'); + return false; + } + return await this.execute(playerSchema, teamsPlugin, sc.get(message, 'remove', playerSchema.id)); + } + + static async execute(playerSchema, teamsPlugin, singleRemoveId) + { + let clanId = playerSchema?.privateData?.clan?.id; + if(!clanId){ + Logger.warning('Clan ID not found in current player.', playerSchema); + return false; + } + let currentClan = teamsPlugin.clans[clanId]; + if(!currentClan){ + Logger.error('Player "'+playerSchema.player_id+'" current clan "'+clanId+'" not found.'); + return false; + } + let playerIds = Object.keys(currentClan.players); + let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; + for(let playerId of removeByKeys){ + let sendUpdate = { + act: TeamsConst.ACTIONS.CLAN_LEFT, + id: currentClan.ownerClient.id, + listener: TeamsConst.KEY + }; + await teamsPlugin.events.emit('reldens.clanLeaveBeforeSendUpdate', { + playerId, + sendUpdate, + singleRemoveId, + playerSchema, + teamsPlugin + }); + currentClan.clients[playerId].send('*', sendUpdate); + currentClan.leave(currentClan.players[playerId]); + delete currentClan.members[playerId]; + await teamsPlugin.dataServer.getEntity('clanMembers').deleteBy({'player_id': playerId}); + } + if(0 === Object.keys(currentClan.members).length){ + let event = {singleRemoveId, playerSchema, teamsPlugin, continueDisband: true}; + await teamsPlugin.events.emit('reldens.beforeClanDisband', event); + if(!event.continueDisband){ + return false; + } + delete teamsPlugin.clans[clanId]; + return teamsPlugin.dataServer.getEntity('clan').deleteById(clanId); + } + let event = {singleRemoveId, playerSchema, teamsPlugin, continueLeave: true}; + await teamsPlugin.events.emit('reldens.clanLeaveAfterSendUpdate', event); + if(!event.continueLeave){ + return false; + } + return ClanUpdatesHandler.updateClanPlayers(currentClan); + } + +} + +module.exports.ClanLeave = ClanLeave; diff --git a/lib/teams/server/message-actions/team-join.js b/lib/teams/server/message-actions/team-join.js index 87c9043de..4f2cc9009 100644 --- a/lib/teams/server/message-actions/team-join.js +++ b/lib/teams/server/message-actions/team-join.js @@ -58,7 +58,7 @@ class TeamJoin if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ return false; } - TeamUpdatesHandler.updateTeamPlayers(currentTeam); + return TeamUpdatesHandler.updateTeamPlayers(currentTeam); } } diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 3e22ea8f2..923cce1e7 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -33,6 +33,7 @@ class TeamLeave playerSchema.currentTeam = false; return false; } + // @NOTE: the way this works is by making the clients leave the team and then updating the remaining players. let playerIds = Object.keys(currentTeam.players); let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ @@ -66,8 +67,7 @@ class TeamLeave if(!event.continueLeave){ return false; } - TeamUpdatesHandler.updateTeamPlayers(currentTeam); - return true; + return TeamUpdatesHandler.updateTeamPlayers(currentTeam); } } diff --git a/lib/teams/server/message-actions/try-clan-invite.js b/lib/teams/server/message-actions/try-clan-invite.js new file mode 100644 index 000000000..7c421b86e --- /dev/null +++ b/lib/teams/server/message-actions/try-clan-invite.js @@ -0,0 +1,40 @@ +/** + * + * Reldens - TryClanInvite + * + */ + +const { TeamsConst } = require('../../constants'); +const { Logger, sc } = require('@reldens/utils'); + +class TryClanInvite +{ + + static async execute(client, data, room, playerSchema, teamsPlugin) + { + if(playerSchema.sessionId === data.id){ + Logger.info('The player is trying to clan up with himself.', playerSchema.sessionId, data); + return false; + } + let toPlayer = sc.get(room.activePlayers, data.id, false); + if(false === toPlayer){ + Logger.error('Player not found.', toPlayer, data); + return false; + } + let sendData = { + act: TeamsConst.ACTIONS.TEAM_INVITE, + listener: TeamsConst.KEY, + from: playerSchema.playerName, + id: playerSchema.sessionId, + }; + let event = {client, data, room, playerSchema, teamsPlugin, continueStart: true}; + await teamsPlugin.events.emit('reldens.tryClanStart', event); + if(!event.continueStart){ + return false; + } + toPlayer.client.send('*', sendData); + return true; + } +} + +module.exports.TryClanInvite = TryClanInvite; diff --git a/lib/teams/server/message-actions/try-team-start.js b/lib/teams/server/message-actions/try-team-start.js index 834e7d7a8..4670e7f91 100644 --- a/lib/teams/server/message-actions/try-team-start.js +++ b/lib/teams/server/message-actions/try-team-start.js @@ -33,7 +33,8 @@ class TryTeamStart return false; } toPlayer.client.send('*', sendData); + return true; } } -module.exports.TryTeamStart = TryTeamStart; \ No newline at end of file +module.exports.TryTeamStart = TryTeamStart; From 2cc3eb0e7c59228d318a3ab70c1dd204eebfcf9d Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Tue, 4 Apr 2023 19:53:35 +0200 Subject: [PATCH 38/82] - Reldens - v4.0.0 - Clan WIP. --- lib/chat/cleaner.js | 10 +- lib/teams/server/clan-factory.js | 33 + .../create-player-clan-handler.js | 24 +- .../server/message-actions/clan-create.js | 56 +- .../server/message-actions/clan-disconnect.js | 4 +- lib/teams/server/message-actions/clan-join.js | 36 +- .../server/message-actions/clan-leave.js | 2 +- package-lock.json | 2271 +++++++++-------- package.json | 4 +- 9 files changed, 1292 insertions(+), 1148 deletions(-) create mode 100644 lib/teams/server/clan-factory.js diff --git a/lib/chat/cleaner.js b/lib/chat/cleaner.js index 36ed85510..3812cf0fd 100644 --- a/lib/chat/cleaner.js +++ b/lib/chat/cleaner.js @@ -2,21 +2,17 @@ * * Reldens - Cleaner * - * Clean chat messages. - * */ +const { sc } = require('@reldens/utils'); + class Cleaner { cleanMessage(message, characterLimit) { // @TODO - BETA - Implement any clean feature here. - let text = message.toString().replace(/\\/g, ''); - if(0 < characterLimit){ - return text.substring(0, characterLimit); - } - return text; + return sc.cleanMessage(message, characterLimit); } } diff --git a/lib/teams/server/clan-factory.js b/lib/teams/server/clan-factory.js new file mode 100644 index 000000000..99d06e8f8 --- /dev/null +++ b/lib/teams/server/clan-factory.js @@ -0,0 +1,33 @@ +/** + * + * Reldens - ClanFactory + * + */ + +const { Clan } = require('./clan'); +const { Logger } = require('@reldens/utils'); + +class ClanFactory +{ + + static async create(clanId, playerOwner, clientOwner, sharedProperties, teamsPlugin) + { + let clanModel = await teamsPlugin.dataServer.getEntity('clan').loadByIdWithRelations( + clanId, + ['members.parent_player, parent_level.modifiers'] + ); + if(!clanModel){ + Logger.error('Clan not found by ID "'+clanId+'".'); + return false; + } + teamsPlugin.clans[clanId] = Clan.fromModel({ + clanModel, + owner: playerOwner, + ownerClient: clientOwner, + sharedProperties + }); + } + +} + +module.exports.ClanFactory = ClanFactory; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 76cc91b38..9c17f9e5e 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -4,7 +4,7 @@ * */ -const { Clan } = require('../clan'); +const { ClanFactory } = require('../clan-factory'); const { ClanUpdatesHandler } = require('../clan-updates-handler'); const { TeamsConst } = require('../../constants'); const { Logger, sc } = require('@reldens/utils'); @@ -40,7 +40,7 @@ class CreatePlayerClanHandler if(!playerSchema.privateData){ playerSchema.privateData = {}; } - playerSchema.privateData.clan = clan; + playerSchema.privateData.clan = clanMemberModel.clan_id; let endEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClanUpdate', endEvent); if(!endEvent.continueProcess){ @@ -52,22 +52,14 @@ class CreatePlayerClanHandler static async loadClan(teamsPlugin, clanId, playerSchema, client, room) { let clan = sc.get(teamsPlugin.clans, clanId, false); - if(false === clan){ - let clanModel = await teamsPlugin.dataServer.getEntity('clan').loadByIdWithRelations( + if(!clan){ + return await ClanFactory.create( clanId, - ['members.parent_player, parent_level.modifiers'] + playerSchema, + client, + room.config.get('client/ui/teams/sharedProperties'), + teamsPlugin ); - if(!clanModel){ - Logger.error('Clan not found by ID "'+clanId+'".'); - return false; - } - clan = Clan.fromModel({ - clanModel, - owner: playerSchema, - ownerClient: client, - sharedProperties: room.config.get('client/ui/teams/sharedProperties') - }); - teamsPlugin.clans[clan.id] = clan; } return clan; } diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index 45adb2675..1306e6c43 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -5,53 +5,74 @@ */ const { TeamsConst } = require('../../constants'); -const { Logger } = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); +const {ClanFactory} = require("../clan-factory"); class ClanCreate { static async execute(client, data, room, playerSchema, teamsPlugin) { - let characterLimit = room.config.getWithoutLogs('server/clan/settings/nameLimit', TeamsConst.NAME_LIMIT); - let clanName = data[TeamsConst.ACTIONS.CLAN_NAME]?.toString().replace(/\\/g, '').substring(0, characterLimit); + let clanName = sc.cleanMessage( + data[TeamsConst.ACTIONS.CLAN_NAME], + room.config.getWithoutLogs('server/clan/settings/nameLimit', TeamsConst.NAME_LIMIT) + ); + if(!clanName){ + Logger.info('Invalida provided Clan name.'); + return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_ERROR, clanName); + } let repository = teamsPlugin.dataServer.getEntity('clan'); let exists = await repository.loadOneBy('name', clanName); if(exists){ - Logger.info('Clan already exists with name "'+clanName+'".'); + Logger.info('Clan name "'+clanName+'" is already taken.'); return this.clanCreateSend(client, TeamsConst.VALIDATION.NAME_EXISTS, clanName); } - let levelsRepository = teamsPlugin.dataServer.getEntity('clanLevels'); - levelsRepository.limit = 1; - levelsRepository.sortBy = 'key'; - let firstLevel = await levelsRepository.loadOne({}); + let firstLevel = await this.fetchInitialLevel(teamsPlugin); if(!firstLevel){ Logger.info('Clan creation invalid level "'+clanName+'".'); return this.clanCreateSend(client, TeamsConst.VALIDATION.LEVEL_ISSUE, clanName); } - levelsRepository.limit = 0; - levelsRepository.sortBy = false; - let createdClan = await repository.create({ + let createdClanModel = await repository.create({ name: clanName, owner_id: playerSchema.player_id, points: room.config.getWithoutLogs('server/clan/settings/startingPoints', TeamsConst.CLAN_STARTING_POINTS), level: firstLevel.key }); - if(!createdClan){ - Logger.info('Clan creation error "'+clanName+'".', createdClan); + if(!createdClanModel){ + Logger.info('Clan creation error "'+clanName+'".', createdClanModel); return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_ERROR, clanName); } let ownerMember = await teamsPlugin.dataServer.getEntity('clanMembers').create({ - clan_id: createdClan.id, + clan_id: createdClanModel.id, player_id: playerSchema.player_id }); if(!ownerMember){ - Logger.info('Clan owner creation error "'+clanName+'".', createdClan, ownerMember); + Logger.info('Clan owner creation error "'+clanName+'".', createdClanModel, ownerMember); return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_OWNER_ERROR, clanName); } + playerSchema.privateData.clan = createdClanModel.id; + await ClanFactory.create( + createdClanModel.id, + playerSchema, + client, + room.config.get('client/ui/teams/sharedProperties'), + teamsPlugin + ); return this.clanCreateSend(client, TeamsConst.VALIDATION.SUCCESS, clanName, playerSchema.player_id); } - static clanCreateSend(client, result, clanName, ownerId) + static async fetchInitialLevel(teamsPlugin) + { + let levelsRepository = teamsPlugin.dataServer.getEntity('clanLevels'); + levelsRepository.limit = 1; + levelsRepository.sortBy = 'key'; + let firstLevel = await levelsRepository.loadOne({}); + levelsRepository.limit = 0; + levelsRepository.sortBy = false; + return firstLevel; + } + + static clanCreateSend(client, result, clanName, ownerId, clanId) { // @TODO - BETA - Include additional event before send. let sendData = { @@ -63,6 +84,9 @@ class ClanCreate if(ownerId){ sendData.id = ownerId; } + if(clanId){ + sendData.clanId = clanId; + } client.send('*', sendData); return true; } diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 1fc64404a..95b8ae1b8 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -6,14 +6,14 @@ const { ClanUpdatesHandler } = require('../clan-updates-handler'); const { TeamsConst } = require('../../constants'); -const { Logger, sc } = require('@reldens/utils'); +const { Logger } = require('@reldens/utils'); class ClanDisconnect { static async execute(playerSchema, teamsPlugin) { - let clanId = playerSchema?.privateData?.clan?.id; + let clanId = playerSchema?.privateData?.clan; if(!clanId){ Logger.warning('Clan ID not found in current player for disconnection.', playerSchema); return false; diff --git a/lib/teams/server/message-actions/clan-join.js b/lib/teams/server/message-actions/clan-join.js index 3eaea99c3..7c1848fc7 100644 --- a/lib/teams/server/message-actions/clan-join.js +++ b/lib/teams/server/message-actions/clan-join.js @@ -17,25 +17,31 @@ class ClanJoin Logger.info('The player is trying to join a clan with himself.', playerSchema.sessionId, data); return false; } - if(playerSchema?.privateData?.clan){ - teamsPlugin.clans[playerSchema.player_id]?.leave(playerSchema); + let previousClanId = playerSchema?.privateData?.clan; + if(previousClanId){ + teamsPlugin.clans[previousClanId]?.leave(playerSchema); } - let clanOwnerPlayer = room.playerByIdFromState(data.id); - if(!clanOwnerPlayer){ - Logger.error('Player clan owner not found.', clanOwnerPlayer, data); + let clanOwnerPlayerSchema = room.playerByIdFromState(data.id); + if(!clanOwnerPlayerSchema){ + Logger.error('Player clan owner not found.', clanOwnerPlayerSchema, data); return false; } - let clanOwnerClient = room.activePlayers[data.id]; - if(!clanOwnerClient){ - Logger.error('Player clan owner client not found.', clanOwnerClient, data); + let clanId = clanOwnerPlayerSchema?.privateData?.clan; + if(!clanId){ + Logger.error('Clan not found in player owner.', clanOwnerPlayerSchema, data); + return false; + } + let clanOwnerActivePlayer = room.activePlayers[data.id]; + if(!clanOwnerActivePlayer){ + Logger.error('Active player clan owner client not found.', clanOwnerActivePlayer, data); return false; } let clanProps = { - owner: clanOwnerPlayer, - ownerClient: clanOwnerClient.client, + owner: clanOwnerActivePlayer, + ownerClient: clanOwnerActivePlayer.client, sharedProperties: room.config.get('client/ui/clans/sharedProperties') }; - let currentClan = teamsPlugin.clans[clanOwnerClient.id]; + let currentClan = teamsPlugin.clans[clanId]; if(!currentClan){ let beforeCreateEvent = {clanProps, teamsPlugin, continueBeforeCreate: true}; await teamsPlugin.events.emit('reldens.beforeClanCreate', beforeCreateEvent); @@ -50,15 +56,11 @@ class ClanJoin return false; } currentClan.join(playerSchema, client); - if(!clanOwnerPlayer.privateData){ - clanOwnerPlayer.privateData = {}; - } - clanOwnerPlayer.privateData.clan = clanOwnerClient.id; if(!playerSchema.privateData){ playerSchema.privateData = {}; } - playerSchema.privateData.clan = clanOwnerClient.id; - teamsPlugin.clans[clanOwnerClient.id] = currentClan; + playerSchema.privateData.clan = currentClan.id; + teamsPlugin.clans[currentClan.id] = currentClan; let eventBeforeJoinUpdate = {currentClan, teamsPlugin, continueBeforeJoinUpdate: true}; await teamsPlugin.events.emit('reldens.beforeClanUpdatePlayers', eventBeforeJoinUpdate); if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ diff --git a/lib/teams/server/message-actions/clan-leave.js b/lib/teams/server/message-actions/clan-leave.js index 495b903c1..4cc259746 100644 --- a/lib/teams/server/message-actions/clan-leave.js +++ b/lib/teams/server/message-actions/clan-leave.js @@ -23,7 +23,7 @@ class ClanLeave static async execute(playerSchema, teamsPlugin, singleRemoveId) { - let clanId = playerSchema?.privateData?.clan?.id; + let clanId = playerSchema?.privateData?.clan; if(!clanId){ Logger.warning('Clan ID not found in current player.', playerSchema); return false; diff --git a/package-lock.json b/package-lock.json index 7eebacc24..ee061452b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,8 +56,8 @@ "@reldens/items-system": "^0.18.1", "@reldens/modifiers": "^0.19.0", "@reldens/skills": "^0.18.0", - "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.19.0", + "@reldens/storage": "^0.14.0", + "@reldens/utils": "^0.20.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", @@ -232,511 +232,502 @@ "optional": true }, "node_modules/@aws-sdk/abort-controller": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.254.0.tgz", - "integrity": "sha512-ZBJFCCU7mIXGLk5GFXrSReyUR/kOBju0kzd7nVAAQQlfkmHZEuFhKFFMXkfJZG0SC0ezCbmR/EzIqJ2mTI+pRA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.303.0.tgz", + "integrity": "sha512-LzNzpeyTppcmV/6SAQI3T/huOkMrUnFveplgVNwJxw+rVqmqmGV6z6vpg+oRICRDcjXWYiSiaClxxSVvOy0sDQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.254.0.tgz", - "integrity": "sha512-FZlTQqgY7v3A2SPq0wR+W1ApJ5hSB0qYezDfaDofpdZbHFKLKyQQr14n5PBrT57/I9Wo75rrgPLENhAqOu3TfQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.303.0.tgz", + "integrity": "sha512-rybplTjq6aj7DttT+v8ycajT8BIFXqdo66lkQjO1YykEIyVTnY4L9McTyNFOZsvNmG1LMSqb95/eYP463Lp7fg==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.254.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sts": "3.303.0", + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.254.0.tgz", - "integrity": "sha512-Ih80wJpGHa4nwxtTQvmSTT1UXQeHweYk+A6y779H1dtczr8h9Y2lXZa0C0TGcd1LEpL0nYHw0kJE3ZBw1/0nhw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.303.0.tgz", + "integrity": "sha512-LZ+Z6vGnEdqmxx0dqtZP97n5VX5uUKu4lJmDR3sdGolxAUqCY1FxHDZd9DzCFXR8rwoJK4VJTL+exzeVp4Ly/g==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.254.0.tgz", - "integrity": "sha512-KRF/hBysJgrZpjRgg47Fa7YzC10Ypqf/FSeesJkN6l2lULosO+o0N+RD4t5LLWXrD10c9by6m1ueC6077z0fGQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.303.0.tgz", + "integrity": "sha512-oOdDcBjxGiJ6mFWUMVr+A1hAzGRpcZ+oLAhCakpvpXCUG50PZSBFP+vOQXgHY/XNolqDg+IHq60oE9HoPzGleg==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.254.0.tgz", - "integrity": "sha512-up+u5ik1pZ9Vo2MtcMCZ6pNAFhrePbH/IBFSgSsJlCU4AsGxPBsm59CE3/n/e84pFzhwVmFEUdavysIM+aP8LA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.303.0.tgz", + "integrity": "sha512-oda7mOfGyJZe62DZ5BVH3L84yeDM0Ja/fSpTjwV9hqFqzgtW83TCpiNegcJmvmGWDYaPmE2qpfDPqPzymB0sBg==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-sdk-sts": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "fast-xml-parser": "4.0.11", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-sdk-sts": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "fast-xml-parser": "4.1.2", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/config-resolver": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.254.0.tgz", - "integrity": "sha512-+t5mi/SrZdAbSgg/5b/q3zVZsNQSyty2XX+znaRvBdANtIWIBdFLEMQp/L5NA+PSiW6VUXu9eXcsj0kJlAhTgQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.303.0.tgz", + "integrity": "sha512-uGZ47jcH86AwWcjZjuOL5jK5qE4izrEol8oF7KY214kjmavbKQstyUqmcwL2lr/YpDNFkCYgUxWRpduqVm8zmw==", "optional": true, "dependencies": { - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-config-provider": "3.295.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.254.0.tgz", - "integrity": "sha512-aZgGDn7lutcLcYp8z6z3PvfoGvrudv5mYj7mf/HDcEiHwpHGu+1t5qUw7VVD4e5yd3OCGJ2Rttohw9EIvXgcmg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.303.0.tgz", + "integrity": "sha512-9MYsGJCNLsm61PW/JFm4y0Cv6aluCkZmE5D/g4vYnEFOZSKyK15m1a10RKGAh391fh6Bg1kU9WOoqkGk3Nyqng==", "optional": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-cognito-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.254.0.tgz", - "integrity": "sha512-2CDwb7L1XGTY7Y8N3EsE1xqas0zNvrs4aOEv5XZNrKqE+9bvs8CiUwV4SB6VwSD+EPcOSm3QYEURUmj5EyLEZQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.303.0.tgz", + "integrity": "sha512-rtXumfF4cGrVk9fWACeLCfdpmlzlDUkzwSR60/3enC5Antcxl3fFY5T1BzNFvz0mB0zcwm4kaAwIcljX67DNRA==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.254.0.tgz", - "integrity": "sha512-sM3N7FLz+svRGjTgwAybKBmu5tVfCJmd5HPEfKR0jfBWB1uq0u0J+65JiO/wfqn/ix+3ZyFfacSJDFjnSPu/KA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.303.0.tgz", + "integrity": "sha512-ruomcFkKUpJkZb87em698//A0AVpt1KN9dn8N8eVyOuvZzebVxSW4AJoVgOKd5Av4PVcZgEqRX0kOOVp0iTrWg==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.254.0.tgz", - "integrity": "sha512-cqzrvuniurfiKmJsOlGyagUUwrZuOnEAxGDL0Olg5Ac3XByAO5AWH/mjy9P7u31j3vxybMz/Uw5kR7lV1q5sXQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.303.0.tgz", + "integrity": "sha512-4J50F6fEjQmAstSQOpJFG+rnbEqtwA7nDG6PxNm98VSTH2mYJV0YgBdvydfBKrKINAT4xYZta5Sc4WEIpSo0TA==", "optional": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.254.0.tgz", - "integrity": "sha512-jjR/qLn0lwDJmeWwTMwWG2zk5GpjCdKts1Taxd5GFHHSzkH/FZG8Vr8u5yWRtoE/x876n+ItiRfcSrLTTwqkUQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.303.0.tgz", + "integrity": "sha512-OlKb7O2jDtrzkzLT/PUb5kxuGGTIyPn2alXzGT+7LdJ9/tP8KlqSVMtnH2UYPPdcc/daK16+MRNL5ylxmnRJ7Q==", "optional": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-ini": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-ini": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.254.0.tgz", - "integrity": "sha512-vNm1AHMu5Lg1kOMk4ucWgaNO4zNAD7aeRssdBMnC7WqRT2xB8CUEWi+zJGNjbxzEeTLXQZuMa1VeRT3nPjYrzg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.303.0.tgz", + "integrity": "sha512-1pxDYRscGlERAjFE5hSF1KQdcyOGzssuRTdLvez4I/mSIOAJLMmBAnmHGI/DME2LzDVrC9dklA6LHSC2sn3quQ==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.254.0.tgz", - "integrity": "sha512-uFfAQ/sIWreDA79HpJqdL+LkVp/yGkdBrDhjbiXIyeVWy/NmQNKu8l0j+wGazk1r562TJbzZ0Gz6+wTsdUSPgA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.303.0.tgz", + "integrity": "sha512-/szzM1BzZGjHwV4mSiZo65cyDleJqnxM9Y4autg55mb3dFwcCiMGI6TGbdegumrNZZlCTeTA1lIhA9PdT4gDAQ==", "optional": true, "dependencies": { - "@aws-sdk/client-sso": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/token-providers": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sso": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/token-providers": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.254.0.tgz", - "integrity": "sha512-R/5qjAoCHEe7xmY5j0vges4xKpFpTgrwzdST822JVNWUobZmiDUqnn+1Xw4Qmomst625NOpgzsV4JuHsA4a8Ig==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.303.0.tgz", + "integrity": "sha512-qi5CP4ocseqdj3kMi0vgLx8XrdanLNvCAfgiEF6LjUJI88R2snZAYNUSd+Y2n04mKAalns+mUwfUN2JyL66d5g==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.254.0.tgz", - "integrity": "sha512-HoBfuSGU7cJ/My1gDLLePLlNjI/bAHQV+Ch8yqkipesfVuiO6aRaB7ipJYZS5OFJffkY9oJkZmGtqmrpfpziMQ==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.254.0", - "@aws-sdk/client-sso": "3.254.0", - "@aws-sdk/client-sts": "3.254.0", - "@aws-sdk/credential-provider-cognito-identity": "3.254.0", - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-ini": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.303.0.tgz", + "integrity": "sha512-ueO8UKvYyzt2lexvIdg50TFC7EO2shRWbMWPsVi6Ul7euoQzthr/TPQts4OLZIt9XeIFd4s9dhFwYSobcRfVGw==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.303.0", + "@aws-sdk/client-sso": "3.303.0", + "@aws-sdk/client-sts": "3.303.0", + "@aws-sdk/credential-provider-cognito-identity": "3.303.0", + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-ini": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.254.0.tgz", - "integrity": "sha512-/bbtNHe5JHFdKnCVr3Zx55sqs4c0F+7f1CC5cvTgH3O46wgIRM/6/rvE0YieXmfm3ho/GOhxBUzy59A0haKQGg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.303.0.tgz", + "integrity": "sha512-Bc6C86/KQOSWPa741h9QEVcApyignYV5vC5+zZjmKkcyPxrVxTmL3kTJidpVOtVfCmTIrNN/WhAVDzLBbh1ycQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/querystring-builder": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/querystring-builder": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/hash-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.254.0.tgz", - "integrity": "sha512-7FoB6BVbO+Z/NEOHeOAoUTyj8q+Pcdn4QpKvA4epRDrzMNcXy7MUNzzt148nkDssES09rgsN+KM8Zo2qgRYngg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.303.0.tgz", + "integrity": "sha512-jSo4A/JxTabZ9jHrx7nhKIXnOmvPg/SSYnoHaFdVS5URJrNt1w+nSvW1wLGMEMOvu5+NU3bldBBSb+h0Ocwv1A==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-buffer-from": "3.208.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-buffer-from": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.254.0.tgz", - "integrity": "sha512-ueV0tXyGndCTZXnEv+AMeTfu+IqV2QzmGMXcakiwxDjg48H9X/bLnj+C96Sexond8jD8K0ub9HWhkBrvvAXlPA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.303.0.tgz", + "integrity": "sha512-RXNcLxOrUJaMMqk5uIYEf6X9XCMockT27bS8Dde/0ms015VOo8Wn2hHU9wEmGeFvLccC2UU4gPzvmj74w70q2Q==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", - "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.303.0.tgz", + "integrity": "sha512-IitBTr+pou7v5BrYLFH/SbIf3g1LIgMhcI3bDXBq2FjzmDftj4bW8BOmg05b9YKf2TrrggvJ4yk/jH+yYFXoJQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.254.0.tgz", - "integrity": "sha512-IT7nDZA6WsaZSNp9M79xfkk/us4kGV4SIZ2R9gHT9MFqdmpmbr3EGhFLKXUHcAZfCcOdw+JNV/wHJiiN1JD/hg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.303.0.tgz", + "integrity": "sha512-0UL5TWSL1JRpjT6gjGsZXfia5oL7vxzj+CfMCqkP6gjVF69eRcgu426Xc6TJwDcr6jIFPeamDBTLyt9ZAAr6hg==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.254.0.tgz", - "integrity": "sha512-9fkDtSJdhEr91tWp4zLyKhHDGVyvUA0gDK+6wGYyorKCae2qX2TL+Fl6vsqY4PxrdTpXRBJDlJnEly9i48YKxg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.303.0.tgz", + "integrity": "sha512-z2i8LJ6YTKbqXh9rY/KbXihvhq6P0JVI6SnkwT2hesJp0Nfldx85jsaLzj1+ioNKlQ+51u9UmBnO404DgNCAbg==", "optional": true, "dependencies": { - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.254.0.tgz", - "integrity": "sha512-JG+OoyCMivnqTYiPZxRF+sgYEyQG68+PMl2843owvSxQQ25nH2Ih6DzLqH10c/uAN0PsiA8s/FfJBzhw9Xf0KA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.303.0.tgz", + "integrity": "sha512-LUyhtjbuosrD0QAsBZJwT3yp146I7Xjehf42OP3dWbRuklMEilI0Res5K2/nknf3/ZKUj6sf7BbJoU8E+SpRiQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.254.0.tgz", - "integrity": "sha512-h3jEw58VUJkfqrwWMmp3Qc8293RFo4LMqxNAVsVwYEG6xb/RQ+JamsOx+t6aDsoOdKqhYngWwDGtgUZQ5wQQvg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.303.0.tgz", + "integrity": "sha512-y2sqmmBdm4gXUL4SyN+ucfO/sxtOEDj2sB12ArRpDGyerfNLhAf7xpL4lXkjPx/7wTIjlBWoO2G/yK6t00P6fA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.254.0.tgz", - "integrity": "sha512-/4tTvAXmIIMCs3giPIXN9aVJUGMoBMWw+9WS22u7nYNzwTe/k30DhS91uvwj7TLOOpFN0IBNXPCJ+T1OZn+ZXQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.303.0.tgz", + "integrity": "sha512-z3MTsZMtPg6hYWl6a0o07q7zgsDXPYeP14XFVMc8NXqiAyNcm/OYwanpXyNjsEKI/X0nlpJ/Rs+IRCbaIqV9Mw==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-retry": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.254.0.tgz", - "integrity": "sha512-nHgris8NmtLzsH5iUA8geW6RAT1VRymjlieKFmM3CAYt2h2X8AtAiL/Wod+Pj3+jjRGk9YeGzOOGbzODHiRxnA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.303.0.tgz", + "integrity": "sha512-wxlqrdGOrCm2Jsra7YyfLyO34YRB/FNlXzwuJiZkqoAb/40ZAuFcWqDv41SP44y8liFXqfsMGuywJ7mK2cHvnA==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/service-error-classification": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-middleware": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "tslib": "^2.3.1", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/service-error-classification": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "tslib": "^2.5.0", "uuid": "^8.3.2" }, "engines": { @@ -744,440 +735,438 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.254.0.tgz", - "integrity": "sha512-Y074nmTp07thuOI6GePv8IKdL/OvkO1tn2l7QvnwQa3Sy/HyNai1V3MVtq4hRi1dgDjheKPVHPE+TnOmF3w5uA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.303.0.tgz", + "integrity": "sha512-igp7htNCUPhVL9Q6rJSgcx3qy/P2l2KAiS0oozOTaTXt3h0LbOusSXtwyA7qvLYeRthnw6msVW+rVBAW3Vo+3g==", "optional": true, "dependencies": { - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-serde": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.254.0.tgz", - "integrity": "sha512-YuItb2nlKADTBItcn68eA8amX4quuR1+0GyFRkwssKS/iTjbIk+3gJ2s1zxkUhlyozH3U38Jvvqd+W9+gNpYIg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.303.0.tgz", + "integrity": "sha512-mmZozwYKgUgXkJrLVqgIYoOQ8DfKZS3pBBT3ZxWzv5Hz5M3oRqFgfVYljkeDM2CTvBweHpqVRTWqPDMcZisucg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.254.0.tgz", - "integrity": "sha512-HMVGf+yANjlKCUMFZJU2PNzbI9hbCgL+IX/Y4DGuQW9cp7EgZOxQre1LBKpcCqqPVQ4toIdfNH/K8uM2fpO6dg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.303.0.tgz", + "integrity": "sha512-rrLQcS2wFsUGj9Kyx78LRgRS8jwiixz/Nyv06SmcKhP680sweETpQz/EA+wcVEVRXmUI6vs4NtqXz36dU0X8Nw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/signature-v4": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-stack": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.254.0.tgz", - "integrity": "sha512-yPWRnjeLC0lPAEQbiqbC3+hnqXZ+uCSoSevGndU5KWMMiXLxKZn7Y0B3kG8NAnNNuPid+wYFWWU9rKiBRvWR/w==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.303.0.tgz", + "integrity": "sha512-6KmdroXLexzILGxF/Xq0cGBs+B8Ipm1pff8qnWCT6KldYp+Q40bVcJrExkVHDN1uOsOxu20ixW2yujOKS356zg==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.254.0.tgz", - "integrity": "sha512-hp5UYRg3ysZXMFMv34nYexyom6Z3pdx+OmisJz4w3AMigT8y57Ps30Vg+1QYaGlQkI4vfvcmdZX2Q+kp+mb9gQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.303.0.tgz", + "integrity": "sha512-ZVMVNxPRn2jXog3V4xWokSYoQxTKAdKlNoCfjqFplsF70r8sXfgZtOMF5ZhGo+Hgsx7GqpR/NWPKJtZD2nigpg==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/node-config-provider": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.254.0.tgz", - "integrity": "sha512-3Bp3Gp2NOY9gab738xf07TysO5iB0Ib9qRNGDlxX8SX8fZDRnxrF2cn+Tjte42wrO54orwhSyuTaIlAqKeii8Q==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.303.0.tgz", + "integrity": "sha512-Ywbo9+2SkbdmNgCoxYJrv+YrFDtBH7hHtn2ywtzP4t57d4t0V/LNrNQsrAsXxqy48OS5r2ovOLHiqJS5jp1oyw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/node-http-handler": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.254.0.tgz", - "integrity": "sha512-DX2WJ3pub+3FF9GpoF5doERCn06MxS/UmmbKnIIokWQHjPZVomNh/1P3Cf9Jn9jeIPgh4UOg0uPD8cUm/cwHQw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.303.0.tgz", + "integrity": "sha512-5Te+mwBIOiQr2nM7/SNVFkvYHOH/CswOmUMV4Gxc7YjuervhrYvVFs2P+lL+c8rfiVMTLWjnJ6JiL2JdJfYgnQ==", "optional": true, "dependencies": { - "@aws-sdk/abort-controller": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/querystring-builder": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/abort-controller": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/querystring-builder": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/property-provider": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.254.0.tgz", - "integrity": "sha512-BLZF/LDFjAgv2ZY0vhThU58k++Aw+SK7qNU7XT0D84q5iWlYRKptQEvSSvIkBSI/rZoppOFhK7W80I8kNNbh+Q==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.303.0.tgz", + "integrity": "sha512-d1qbn0pCz+jvB0dcWMWuIlWYM8dWCg3185ngMgUQxkgUk7/kEbwGBsmT+xtZAMQcwcgPkSm8qeATEQ7ToiH8eQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/protocol-http": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.254.0.tgz", - "integrity": "sha512-4o/I/qhMUTp70njwWe3ttyRJSAKegnr8l3oVWAf1/q1ZHpcxbRRZEDvrkx4KSunFeXTTGHcff1oyLSRG/cKMsQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.303.0.tgz", + "integrity": "sha512-eqblSsdmKBzgNl06dUnL4toq/OQgZyxVsxHCz2nI/xBk5lI/qAZIJyEgP2GmP8aoWwneAq33roG0VLZoxQ8exg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/querystring-builder": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.254.0.tgz", - "integrity": "sha512-Er+pOGTrPxelrzggibduO+eB1ClaU2BhjA8gd0nORS3kqktQggG3tKmRSIilegi9WOa3awCk6CnnuAf0pBrbUA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.303.0.tgz", + "integrity": "sha512-0eMp2gd7Ro0svJ6YVnp9cUiGtrc1d/HynyMfbDkLkqWJAnHMz7Oc1GjK5YyL1hdxm0W+JWZCPR0SovLiaboKDw==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-uri-escape": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/querystring-parser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.254.0.tgz", - "integrity": "sha512-WwRD99dwGo2aIrRjLHUAXaWCZ+3fj88IhIwciWTqrHBS3TQWXllOOQmYo7f+aMBB4Q1K6KdKITNi8L7aUuDv2g==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.303.0.tgz", + "integrity": "sha512-KNJSQiTFiA7W5eYCox8bLGM7kghC3Azad86HQhdsYO0jCoPxcgj8MeP6T7fPTIC4WcTwcWb7T1MpzoeBiKMOTQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/service-error-classification": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.254.0.tgz", - "integrity": "sha512-8GHqMJBBF9yoMBG/Nf9PusUSMFjG8ygps/cSJPlgcG2vbFn8BCdBZVc4ptXqICZUnBB/6lrxy8nCmNUaru48jg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.303.0.tgz", + "integrity": "sha512-eO13PzdtRO9C+g3tyFOpIblX2SbDrIbg2bNtB8JOfjVi3E1b5VsSTXXU/cKV+lbZ9XMzMn3VzGSvpo6AjzfpxA==", "optional": true, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.254.0.tgz", - "integrity": "sha512-UH4YTXuG+q004vA+jNrVhrD5XQCIAgpL/eriObJnQpKUVef1mkkEDHZs8+8+ZPsk4p/iBrIJ3lXNf7iDA/BFzw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.303.0.tgz", + "integrity": "sha512-yI84mnnh3pdQtIOo+oGWofaI0rvfhp3DOavB8KHIkQr+RcjF+fxsqbelRfVb25gx7yEWPNCMB8wM+HhklSEFJg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/signature-v4": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.254.0.tgz", - "integrity": "sha512-9FoEnipA9hAgEp6oqIT3+hobF+JgIXIn5QV8kAB7QGxEDqs/pdpEbGc9qbxi0ghdjvqzOSDir9gNI3w0cL8Aug==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.303.0.tgz", + "integrity": "sha512-muw5yclLOgXPHIxv60mhO6R0GVjKbf+M6E/cWvIEVGq8Ke+mLMYNFYNdKP/f/8JgTtW2xwQ7pIK3U8x284ZqPw==", "optional": true, "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.254.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/is-array-buffer": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-hex-encoding": "3.295.0", + "@aws-sdk/util-middleware": "3.303.0", + "@aws-sdk/util-uri-escape": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/smithy-client": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.254.0.tgz", - "integrity": "sha512-SI0jz9JfWi1IaakDX/26xliKTIMJpzwwDoyQPEfZ/L0KKdpr2gNhljA3sR2pZ2EM1oqOaXpMHAunSzv7EBpBWg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.303.0.tgz", + "integrity": "sha512-WDTC9ODdpRAXo8+Mtr5hsPJeR3y3LxfZZFg5dplJgkaxV+MFdnsUCxZfAZMnxcGy5Q2qTzlLLNk9CpadS72v+g==", "optional": true, "dependencies": { - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.254.0.tgz", - "integrity": "sha512-i3W+YWrMtgdFPDWW/m56xrkBhqrB6beKgQi46oSM/aFZ3ZAkFJLfbsLK99LWzVxtzELTSFjJWY54r+Au/hb2kQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.303.0.tgz", + "integrity": "sha512-7G7VYbqyX0v6RTD/m7XmArZToMek4jYXR/TuuGHK6ifNJeMDwkU4BcoVDj37vvTPYp6qKU5IE+bE3XmPyVWnGQ==", "optional": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sso-oidc": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.254.0.tgz", - "integrity": "sha512-xDEDk6ZAGFO0URPgB6R2mvQANYlojHLjLC9zzOzl07F+uqYS30yZDIg4UFcqPt/x48v7mxlKZpbaZgYI2ZLgGA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.303.0.tgz", + "integrity": "sha512-H+Cy8JDTsK87MID6MJbV9ad5xdS9YvaLZSeveC2Zs1WNu2Rp6X9j+mg3EqDSmBKUQVAFRy2b+CSKkH3nnBMedw==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/url-parser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.254.0.tgz", - "integrity": "sha512-Za0JGUa9p5GQ8t2tVtKaRSjLUxrmEdnBlUiZ2zKm86wFxgQnjbMwzD3mvyJ5OaVsXScU5vzc3CXHIXSvS7h7Ng==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.303.0.tgz", + "integrity": "sha512-PXMXGhr89s0MiPTf8Ft/v3sPzh2geSrFhTVSO/01blfBQqtuu0JMqORhLheOdi16AhQNVlYHDW2tWdx7/T+KsA==", "optional": true, "dependencies": { - "@aws-sdk/querystring-parser": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/querystring-parser": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-base64": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", - "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.303.0.tgz", + "integrity": "sha512-oj+p/GHHPcZEKjiiOHU/CyNQeh8i+8dfMMzU+VGdoK5jHaVG8h2b+V7GPf7I4wDkG2ySCK5b5Jw5NUHwdTJ13Q==", "optional": true, "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", - "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.303.0.tgz", + "integrity": "sha512-T643m0pKzgjAvPFy4W8zL+aszG3T22U8hb6stlMvT0z++Smv8QfIvkIkXjWyH2KlOt5GKliHwdOv8SAi0FSMJQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", - "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.303.0.tgz", + "integrity": "sha512-/hS8z6e18Le60hJr2TUIFoUjUiAsnQsuDn6DxX74GXhMOHeSwZDJ9jHF39quYkNMmAE37GrVH4MI9vE0pN27qw==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", - "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.303.0.tgz", + "integrity": "sha512-hUU+NW+SW6RNojtAKnnmz+tDShVKlEx2YsS4a5fSfrKRUes+zWz10cxVX0RQfysd3R6tdSHhbjsSj8eCIybheg==", "optional": true, "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/is-array-buffer": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-config-provider": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", - "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.295.0.tgz", + "integrity": "sha512-/5Dl1aV2yI8YQjqwmg4RTnl/E9NmNsx7HIwBZt+dTcOrM0LMUwczQBFFcLyqCj/qv5y+VsvLoAAA/OiBT7hb3w==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.254.0.tgz", - "integrity": "sha512-vj/s+BuqNKTHN9bsZ/HY7vpBWbo3F+4c3/ZoKSZa5Jc7jAuGCbx3zWwHdJFDgvbqLvsTBw80Q9d/CDy9pKj/tQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.303.0.tgz", + "integrity": "sha512-jtZgCKelFe4/SHDHQu9ydbYttxSfqSlQojA5qxTJxLvzryIB+/GTHQ+sYWyMyzaD489W9elt1/cSsXd4LtPK0A==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.254.0.tgz", - "integrity": "sha512-gvD2+Uf60c2BgUYv2d6R4dSpO/CbvybqblgF8lKZCsHkDWzfEdPv9nlJgUWM1cuMKQ0hBZ3cL3ilOwVKRVPyiQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.303.0.tgz", + "integrity": "sha512-c86iyot/u9bCVcy/rlWL+0kdR51c7C2d2yDXvO9iFCdMKAs28Hw1ijGczVmOcUQ61zKNFSGYx+VekHXN9IWYOg==", "optional": true, "dependencies": { - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.254.0.tgz", - "integrity": "sha512-BzBIOnhVrs4RFTpGZErZfAV1VhqWglxn047VYijmCQe8Aejq4mJAaepSwHYar++XC0+pduD5YO8IidW8z/1vQQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.303.0.tgz", + "integrity": "sha512-dPg9+l3VY3nclWFiWAVNWek5lQwgdtY8oRYOgCeyntce9FlNrPQgCRTVr36D0iQ0aNCs0GWzfjgL+rIdCF66/w==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", - "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.295.0.tgz", + "integrity": "sha512-XJcoVo41kHzhe28PBm/rqt5mdCp8R6abwiW9ug1dA6FOoPUO8kBUxDv6xaOmA2hfRvd2ocFfBXaUCBqUowkGcQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", - "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.295.0.tgz", + "integrity": "sha512-d/s+zhUx5Kh4l/ecMP/TBjzp1GR/g89Q4nWH6+wH5WgdHsK+LG+vmsk6mVNuP/8wsCofYG4NBqp5Ulbztbm9QA==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-middleware": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.254.0.tgz", - "integrity": "sha512-gn7vInNTRBo2QatOB+uU99JwV53wf/zlTUnUK0qOuebtSDLMdiO+msiMi2ctz9vMIrtc2XMXNQro1aE0aUPy4w==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.303.0.tgz", + "integrity": "sha512-HAfBcbZw1+pY3dIEDM4jVpH1ViFcGH5s0q1dr+x4rcLGpMM3B4dH0HUgDPtycG8sw+nk+9jGgiEtgaCNOpJLGA==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-retry": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.254.0.tgz", - "integrity": "sha512-IVA4wAOJpVssEIbJmeq1fdDYvrkOqYFK9Pz4tERmMz33003fyY92dU468Lulw8MnsSALYiwWUoWSFg9L5RCTug==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.303.0.tgz", + "integrity": "sha512-RWwRNjoWMcpDouz69wPuFXWFVzwYtUkTbJfa46SjKl1IwqMHS4f9yjJfCwJIoLOW9M/o2JB7nD0Ij3gqqzajLw==", "optional": true, "dependencies": { - "@aws-sdk/service-error-classification": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/service-error-classification": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.303.0.tgz", + "integrity": "sha512-N3ULNuHCL3QzAlCTY+XRRkRQTYCTU8RRuzFCJX0pDpz9t2K+tLT7DbxqupWGNFGl5Xlulf1Is14J3BP/Dx91rA==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.254.0.tgz", - "integrity": "sha512-2HvwH8l7ln4qTDsU3rgH9NvSSo5qhX+2Lenb6XvNnIMkL4r/tPhNIaGKtoQRfpzLH378Mm9XEQnJM5UXFRWuTA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.303.0.tgz", + "integrity": "sha512-Kex3abpUrTX9z129jiI8sfjIUmQDwiWjhkvBkPmrwjFY/sZcnOcXj5nP2iwJ+k6CnA5ZK5PjZ6P62t+eJ5MTXw==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.254.0", + "@aws-sdk/types": "3.303.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.254.0.tgz", - "integrity": "sha512-6nc9bmRP+2JqbBJ5oRZZRU8l35X3VcWF5j8XvmamWjIABsanc6Gv6NV4qAa3imPjIyWNiShZn/YkTBYs1exsdg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.303.0.tgz", + "integrity": "sha512-QYUg8F/Ho6AsVZaSSRMf/LWoEPDyOwgKZBw3AbKoH6RxAdAsdL1SXz5t4A6jHakP9TLVN2Yw2WRbHDe4LATASQ==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" @@ -1192,38 +1181,25 @@ } }, "node_modules/@aws-sdk/util-utf8": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", - "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.303.0.tgz", + "integrity": "sha512-tZXVuMOIONPOuOGBs/XRdzxv6jUvTM620dRFFIHZwlGiW8bo0x0LlonrzDAJZA4e9ZwmxJIj8Ji13WVRBGvZWg==", "optional": true, "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.303.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", - "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz", - "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==", + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" } }, "node_modules/@babel/code-frame": { @@ -4092,16 +4068,16 @@ } }, "node_modules/@mikro-orm/core": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.7.tgz", - "integrity": "sha512-XLCY3S10nz7uUFGTFypUuaO48cQAhCUiSsQBxpqqhlOqVz6yxGYdqAe7SmMKuplmpOoVL8OGDZrULzd0bIjtYA==", + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.15.tgz", + "integrity": "sha512-LcyHVEW6RO6o1ZA1mVdNypsdx5uGkV19LkKNv4GS88cJxTXzYuuvthv3qZNbrTfDqJojgf+dcI3wQn/9Pbi0Cw==", "dependencies": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", "fs-extra": "11.1.0", "globby": "11.1.0", - "mikro-orm": "~5.6.7", + "mikro-orm": "~5.6.15", "reflect-metadata": "0.1.13" }, "engines": { @@ -4153,9 +4129,9 @@ } }, "node_modules/@mikro-orm/mongodb": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.7.tgz", - "integrity": "sha512-aRrh9IKiEARKrG1RI87kc1rKWnQChMCkpte21YJhKPUjGlEhnGIkB/yY34edXfhxYvjQPmkhcDXgOe58oOk6WA==", + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.15.tgz", + "integrity": "sha512-nBHVWLP67ZOVyTlQV+Mvf0RIyi75vfJkU6jsKRSTr8vhnQTizkMxYwyTYUyt1GzlJwajqgwpxQEf57w9SXpE3Q==", "dependencies": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -5471,6 +5447,35 @@ "@reldens/utils": "^0.19.0" } }, + "node_modules/@reldens/items-system/node_modules/@reldens/storage": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", + "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "dependencies": { + "@mikro-orm/core": "^5.6.7", + "@mikro-orm/mongodb": "^5.6.7", + "@reldens/utils": "^0.18.0", + "knex": "^2.4.1", + "mysql": "^2.18.1", + "objection": "^3.0.1" + } + }, + "node_modules/@reldens/items-system/node_modules/@reldens/storage/node_modules/@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, + "node_modules/@reldens/items-system/node_modules/@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@reldens/modifiers": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.0.tgz", @@ -5479,6 +5484,14 @@ "@reldens/utils": "^0.19.0" } }, + "node_modules/@reldens/modifiers/node_modules/@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@reldens/skills": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@reldens/skills/-/skills-0.18.0.tgz", @@ -5489,7 +5502,7 @@ "@reldens/utils": "^0.19.0" } }, - "node_modules/@reldens/storage": { + "node_modules/@reldens/skills/node_modules/@reldens/storage": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", @@ -5502,7 +5515,7 @@ "objection": "^3.0.1" } }, - "node_modules/@reldens/storage/node_modules/@reldens/utils": { + "node_modules/@reldens/skills/node_modules/@reldens/storage/node_modules/@reldens/utils": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", @@ -5510,7 +5523,7 @@ "await-event-emitter": "^2.0.2" } }, - "node_modules/@reldens/utils": { + "node_modules/@reldens/skills/node_modules/@reldens/utils": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", @@ -5518,6 +5531,35 @@ "await-event-emitter": "^2.0.2" } }, + "node_modules/@reldens/storage": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.14.0.tgz", + "integrity": "sha512-z87VGmJiHO6kKCYIMxeZWcIgZzisw1DINfDq9unlh5ewO4rIgabOdcl1LKBOQpf0TJ3EesVD77C1eJu8B6weHg==", + "dependencies": { + "@mikro-orm/core": "^5.6.15", + "@mikro-orm/mongodb": "^5.6.15", + "@reldens/utils": "^0.19.0", + "knex": "^2.4.2", + "mysql": "^2.18.1", + "objection": "^3.0.1" + } + }, + "node_modules/@reldens/storage/node_modules/@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, + "node_modules/@reldens/utils": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.20.0.tgz", + "integrity": "sha512-WzipJU4yoH68mVWh1+oTEI7ZVoxRTLli81ZI74v1KK16zihFG3YoJ5TIIQg+7BJMTjEkUXj3+PoEGRTON8KjLQ==", + "dependencies": { + "await-event-emitter": "^2.0.2" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -7694,9 +7736,9 @@ "optional": true }, "node_modules/fast-xml-parser": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", - "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", + "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, "dependencies": { "strnum": "^1.0.5" @@ -8999,9 +9041,9 @@ } }, "node_modules/knex": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", - "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz", + "integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==", "dependencies": { "colorette": "2.0.19", "commander": "^9.1.0", @@ -9440,9 +9482,9 @@ } }, "node_modules/mikro-orm": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.7.tgz", - "integrity": "sha512-mxkg+BDXmuQh4PYlWkv0Yysy33Yvg/31BZN4UWWZpV9gcLMYPQcaEF8qfSW7sMbrdNSJcvqf2MX6etmIARHJ9A==", + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.15.tgz", + "integrity": "sha512-DlErpPXJ1gOqbDWgRXq5yEuhuOZP5QXC9EoP8n0OL8klB/Nm/qNRN0ATe0+9TyYdQHNe8BdpPMTwDee56VzdVA==", "engines": { "node": ">= 14.0.0" } @@ -12037,9 +12079,9 @@ } }, "node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/type-fest": { "version": "0.20.2", @@ -12658,824 +12700,803 @@ } }, "@aws-sdk/abort-controller": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.254.0.tgz", - "integrity": "sha512-ZBJFCCU7mIXGLk5GFXrSReyUR/kOBju0kzd7nVAAQQlfkmHZEuFhKFFMXkfJZG0SC0ezCbmR/EzIqJ2mTI+pRA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.303.0.tgz", + "integrity": "sha512-LzNzpeyTppcmV/6SAQI3T/huOkMrUnFveplgVNwJxw+rVqmqmGV6z6vpg+oRICRDcjXWYiSiaClxxSVvOy0sDQ==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/client-cognito-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.254.0.tgz", - "integrity": "sha512-FZlTQqgY7v3A2SPq0wR+W1ApJ5hSB0qYezDfaDofpdZbHFKLKyQQr14n5PBrT57/I9Wo75rrgPLENhAqOu3TfQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.303.0.tgz", + "integrity": "sha512-rybplTjq6aj7DttT+v8ycajT8BIFXqdo66lkQjO1YykEIyVTnY4L9McTyNFOZsvNmG1LMSqb95/eYP463Lp7fg==", "optional": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.254.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sts": "3.303.0", + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/client-sso": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.254.0.tgz", - "integrity": "sha512-Ih80wJpGHa4nwxtTQvmSTT1UXQeHweYk+A6y779H1dtczr8h9Y2lXZa0C0TGcd1LEpL0nYHw0kJE3ZBw1/0nhw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.303.0.tgz", + "integrity": "sha512-LZ+Z6vGnEdqmxx0dqtZP97n5VX5uUKu4lJmDR3sdGolxAUqCY1FxHDZd9DzCFXR8rwoJK4VJTL+exzeVp4Ly/g==", "optional": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/client-sso-oidc": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.254.0.tgz", - "integrity": "sha512-KRF/hBysJgrZpjRgg47Fa7YzC10Ypqf/FSeesJkN6l2lULosO+o0N+RD4t5LLWXrD10c9by6m1ueC6077z0fGQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.303.0.tgz", + "integrity": "sha512-oOdDcBjxGiJ6mFWUMVr+A1hAzGRpcZ+oLAhCakpvpXCUG50PZSBFP+vOQXgHY/XNolqDg+IHq60oE9HoPzGleg==", "optional": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/client-sts": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.254.0.tgz", - "integrity": "sha512-up+u5ik1pZ9Vo2MtcMCZ6pNAFhrePbH/IBFSgSsJlCU4AsGxPBsm59CE3/n/e84pFzhwVmFEUdavysIM+aP8LA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.303.0.tgz", + "integrity": "sha512-oda7mOfGyJZe62DZ5BVH3L84yeDM0Ja/fSpTjwV9hqFqzgtW83TCpiNegcJmvmGWDYaPmE2qpfDPqPzymB0sBg==", "optional": true, "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/fetch-http-handler": "3.254.0", - "@aws-sdk/hash-node": "3.254.0", - "@aws-sdk/invalid-dependency": "3.254.0", - "@aws-sdk/middleware-content-length": "3.254.0", - "@aws-sdk/middleware-endpoint": "3.254.0", - "@aws-sdk/middleware-host-header": "3.254.0", - "@aws-sdk/middleware-logger": "3.254.0", - "@aws-sdk/middleware-recursion-detection": "3.254.0", - "@aws-sdk/middleware-retry": "3.254.0", - "@aws-sdk/middleware-sdk-sts": "3.254.0", - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/middleware-user-agent": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/node-http-handler": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/smithy-client": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.254.0", - "@aws-sdk/util-defaults-mode-node": "3.254.0", - "@aws-sdk/util-endpoints": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "@aws-sdk/util-user-agent-browser": "3.254.0", - "@aws-sdk/util-user-agent-node": "3.254.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "fast-xml-parser": "4.0.11", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/fetch-http-handler": "3.303.0", + "@aws-sdk/hash-node": "3.303.0", + "@aws-sdk/invalid-dependency": "3.303.0", + "@aws-sdk/middleware-content-length": "3.303.0", + "@aws-sdk/middleware-endpoint": "3.303.0", + "@aws-sdk/middleware-host-header": "3.303.0", + "@aws-sdk/middleware-logger": "3.303.0", + "@aws-sdk/middleware-recursion-detection": "3.303.0", + "@aws-sdk/middleware-retry": "3.303.0", + "@aws-sdk/middleware-sdk-sts": "3.303.0", + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/middleware-user-agent": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/node-http-handler": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/smithy-client": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "@aws-sdk/util-body-length-browser": "3.303.0", + "@aws-sdk/util-body-length-node": "3.303.0", + "@aws-sdk/util-defaults-mode-browser": "3.303.0", + "@aws-sdk/util-defaults-mode-node": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "@aws-sdk/util-user-agent-browser": "3.303.0", + "@aws-sdk/util-user-agent-node": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "fast-xml-parser": "4.1.2", + "tslib": "^2.5.0" } }, "@aws-sdk/config-resolver": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.254.0.tgz", - "integrity": "sha512-+t5mi/SrZdAbSgg/5b/q3zVZsNQSyty2XX+znaRvBdANtIWIBdFLEMQp/L5NA+PSiW6VUXu9eXcsj0kJlAhTgQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.303.0.tgz", + "integrity": "sha512-uGZ47jcH86AwWcjZjuOL5jK5qE4izrEol8oF7KY214kjmavbKQstyUqmcwL2lr/YpDNFkCYgUxWRpduqVm8zmw==", "optional": true, "requires": { - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-config-provider": "3.295.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-cognito-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.254.0.tgz", - "integrity": "sha512-aZgGDn7lutcLcYp8z6z3PvfoGvrudv5mYj7mf/HDcEiHwpHGu+1t5qUw7VVD4e5yd3OCGJ2Rttohw9EIvXgcmg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.303.0.tgz", + "integrity": "sha512-9MYsGJCNLsm61PW/JFm4y0Cv6aluCkZmE5D/g4vYnEFOZSKyK15m1a10RKGAh391fh6Bg1kU9WOoqkGk3Nyqng==", "optional": true, "requires": { - "@aws-sdk/client-cognito-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-cognito-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-env": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.254.0.tgz", - "integrity": "sha512-2CDwb7L1XGTY7Y8N3EsE1xqas0zNvrs4aOEv5XZNrKqE+9bvs8CiUwV4SB6VwSD+EPcOSm3QYEURUmj5EyLEZQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.303.0.tgz", + "integrity": "sha512-rtXumfF4cGrVk9fWACeLCfdpmlzlDUkzwSR60/3enC5Antcxl3fFY5T1BzNFvz0mB0zcwm4kaAwIcljX67DNRA==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-imds": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.254.0.tgz", - "integrity": "sha512-sM3N7FLz+svRGjTgwAybKBmu5tVfCJmd5HPEfKR0jfBWB1uq0u0J+65JiO/wfqn/ix+3ZyFfacSJDFjnSPu/KA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.303.0.tgz", + "integrity": "sha512-ruomcFkKUpJkZb87em698//A0AVpt1KN9dn8N8eVyOuvZzebVxSW4AJoVgOKd5Av4PVcZgEqRX0kOOVp0iTrWg==", "optional": true, "requires": { - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-ini": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.254.0.tgz", - "integrity": "sha512-cqzrvuniurfiKmJsOlGyagUUwrZuOnEAxGDL0Olg5Ac3XByAO5AWH/mjy9P7u31j3vxybMz/Uw5kR7lV1q5sXQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.303.0.tgz", + "integrity": "sha512-4J50F6fEjQmAstSQOpJFG+rnbEqtwA7nDG6PxNm98VSTH2mYJV0YgBdvydfBKrKINAT4xYZta5Sc4WEIpSo0TA==", "optional": true, "requires": { - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.254.0.tgz", - "integrity": "sha512-jjR/qLn0lwDJmeWwTMwWG2zk5GpjCdKts1Taxd5GFHHSzkH/FZG8Vr8u5yWRtoE/x876n+ItiRfcSrLTTwqkUQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.303.0.tgz", + "integrity": "sha512-OlKb7O2jDtrzkzLT/PUb5kxuGGTIyPn2alXzGT+7LdJ9/tP8KlqSVMtnH2UYPPdcc/daK16+MRNL5ylxmnRJ7Q==", "optional": true, "requires": { - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-ini": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-ini": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-process": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.254.0.tgz", - "integrity": "sha512-vNm1AHMu5Lg1kOMk4ucWgaNO4zNAD7aeRssdBMnC7WqRT2xB8CUEWi+zJGNjbxzEeTLXQZuMa1VeRT3nPjYrzg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.303.0.tgz", + "integrity": "sha512-1pxDYRscGlERAjFE5hSF1KQdcyOGzssuRTdLvez4I/mSIOAJLMmBAnmHGI/DME2LzDVrC9dklA6LHSC2sn3quQ==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-sso": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.254.0.tgz", - "integrity": "sha512-uFfAQ/sIWreDA79HpJqdL+LkVp/yGkdBrDhjbiXIyeVWy/NmQNKu8l0j+wGazk1r562TJbzZ0Gz6+wTsdUSPgA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.303.0.tgz", + "integrity": "sha512-/szzM1BzZGjHwV4mSiZo65cyDleJqnxM9Y4autg55mb3dFwcCiMGI6TGbdegumrNZZlCTeTA1lIhA9PdT4gDAQ==", "optional": true, "requires": { - "@aws-sdk/client-sso": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/token-providers": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sso": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/token-providers": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.254.0.tgz", - "integrity": "sha512-R/5qjAoCHEe7xmY5j0vges4xKpFpTgrwzdST822JVNWUobZmiDUqnn+1Xw4Qmomst625NOpgzsV4JuHsA4a8Ig==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.303.0.tgz", + "integrity": "sha512-qi5CP4ocseqdj3kMi0vgLx8XrdanLNvCAfgiEF6LjUJI88R2snZAYNUSd+Y2n04mKAalns+mUwfUN2JyL66d5g==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/credential-providers": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.254.0.tgz", - "integrity": "sha512-HoBfuSGU7cJ/My1gDLLePLlNjI/bAHQV+Ch8yqkipesfVuiO6aRaB7ipJYZS5OFJffkY9oJkZmGtqmrpfpziMQ==", - "optional": true, - "requires": { - "@aws-sdk/client-cognito-identity": "3.254.0", - "@aws-sdk/client-sso": "3.254.0", - "@aws-sdk/client-sts": "3.254.0", - "@aws-sdk/credential-provider-cognito-identity": "3.254.0", - "@aws-sdk/credential-provider-env": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/credential-provider-ini": "3.254.0", - "@aws-sdk/credential-provider-node": "3.254.0", - "@aws-sdk/credential-provider-process": "3.254.0", - "@aws-sdk/credential-provider-sso": "3.254.0", - "@aws-sdk/credential-provider-web-identity": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.303.0.tgz", + "integrity": "sha512-ueO8UKvYyzt2lexvIdg50TFC7EO2shRWbMWPsVi6Ul7euoQzthr/TPQts4OLZIt9XeIFd4s9dhFwYSobcRfVGw==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.303.0", + "@aws-sdk/client-sso": "3.303.0", + "@aws-sdk/client-sts": "3.303.0", + "@aws-sdk/credential-provider-cognito-identity": "3.303.0", + "@aws-sdk/credential-provider-env": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/credential-provider-ini": "3.303.0", + "@aws-sdk/credential-provider-node": "3.303.0", + "@aws-sdk/credential-provider-process": "3.303.0", + "@aws-sdk/credential-provider-sso": "3.303.0", + "@aws-sdk/credential-provider-web-identity": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/fetch-http-handler": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.254.0.tgz", - "integrity": "sha512-/bbtNHe5JHFdKnCVr3Zx55sqs4c0F+7f1CC5cvTgH3O46wgIRM/6/rvE0YieXmfm3ho/GOhxBUzy59A0haKQGg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.303.0.tgz", + "integrity": "sha512-Bc6C86/KQOSWPa741h9QEVcApyignYV5vC5+zZjmKkcyPxrVxTmL3kTJidpVOtVfCmTIrNN/WhAVDzLBbh1ycQ==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/querystring-builder": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-base64": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/querystring-builder": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-base64": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/hash-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.254.0.tgz", - "integrity": "sha512-7FoB6BVbO+Z/NEOHeOAoUTyj8q+Pcdn4QpKvA4epRDrzMNcXy7MUNzzt148nkDssES09rgsN+KM8Zo2qgRYngg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.303.0.tgz", + "integrity": "sha512-jSo4A/JxTabZ9jHrx7nhKIXnOmvPg/SSYnoHaFdVS5URJrNt1w+nSvW1wLGMEMOvu5+NU3bldBBSb+h0Ocwv1A==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-buffer-from": "3.208.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-buffer-from": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/invalid-dependency": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.254.0.tgz", - "integrity": "sha512-ueV0tXyGndCTZXnEv+AMeTfu+IqV2QzmGMXcakiwxDjg48H9X/bLnj+C96Sexond8jD8K0ub9HWhkBrvvAXlPA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.303.0.tgz", + "integrity": "sha512-RXNcLxOrUJaMMqk5uIYEf6X9XCMockT27bS8Dde/0ms015VOo8Wn2hHU9wEmGeFvLccC2UU4gPzvmj74w70q2Q==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/is-array-buffer": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", - "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.303.0.tgz", + "integrity": "sha512-IitBTr+pou7v5BrYLFH/SbIf3g1LIgMhcI3bDXBq2FjzmDftj4bW8BOmg05b9YKf2TrrggvJ4yk/jH+yYFXoJQ==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-content-length": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.254.0.tgz", - "integrity": "sha512-IT7nDZA6WsaZSNp9M79xfkk/us4kGV4SIZ2R9gHT9MFqdmpmbr3EGhFLKXUHcAZfCcOdw+JNV/wHJiiN1JD/hg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.303.0.tgz", + "integrity": "sha512-0UL5TWSL1JRpjT6gjGsZXfia5oL7vxzj+CfMCqkP6gjVF69eRcgu426Xc6TJwDcr6jIFPeamDBTLyt9ZAAr6hg==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-endpoint": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.254.0.tgz", - "integrity": "sha512-9fkDtSJdhEr91tWp4zLyKhHDGVyvUA0gDK+6wGYyorKCae2qX2TL+Fl6vsqY4PxrdTpXRBJDlJnEly9i48YKxg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.303.0.tgz", + "integrity": "sha512-z2i8LJ6YTKbqXh9rY/KbXihvhq6P0JVI6SnkwT2hesJp0Nfldx85jsaLzj1+ioNKlQ+51u9UmBnO404DgNCAbg==", "optional": true, "requires": { - "@aws-sdk/middleware-serde": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/url-parser": "3.254.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-serde": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/url-parser": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-host-header": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.254.0.tgz", - "integrity": "sha512-JG+OoyCMivnqTYiPZxRF+sgYEyQG68+PMl2843owvSxQQ25nH2Ih6DzLqH10c/uAN0PsiA8s/FfJBzhw9Xf0KA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.303.0.tgz", + "integrity": "sha512-LUyhtjbuosrD0QAsBZJwT3yp146I7Xjehf42OP3dWbRuklMEilI0Res5K2/nknf3/ZKUj6sf7BbJoU8E+SpRiQ==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-logger": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.254.0.tgz", - "integrity": "sha512-h3jEw58VUJkfqrwWMmp3Qc8293RFo4LMqxNAVsVwYEG6xb/RQ+JamsOx+t6aDsoOdKqhYngWwDGtgUZQ5wQQvg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.303.0.tgz", + "integrity": "sha512-y2sqmmBdm4gXUL4SyN+ucfO/sxtOEDj2sB12ArRpDGyerfNLhAf7xpL4lXkjPx/7wTIjlBWoO2G/yK6t00P6fA==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.254.0.tgz", - "integrity": "sha512-/4tTvAXmIIMCs3giPIXN9aVJUGMoBMWw+9WS22u7nYNzwTe/k30DhS91uvwj7TLOOpFN0IBNXPCJ+T1OZn+ZXQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.303.0.tgz", + "integrity": "sha512-z3MTsZMtPg6hYWl6a0o07q7zgsDXPYeP14XFVMc8NXqiAyNcm/OYwanpXyNjsEKI/X0nlpJ/Rs+IRCbaIqV9Mw==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-retry": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.254.0.tgz", - "integrity": "sha512-nHgris8NmtLzsH5iUA8geW6RAT1VRymjlieKFmM3CAYt2h2X8AtAiL/Wod+Pj3+jjRGk9YeGzOOGbzODHiRxnA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.303.0.tgz", + "integrity": "sha512-wxlqrdGOrCm2Jsra7YyfLyO34YRB/FNlXzwuJiZkqoAb/40ZAuFcWqDv41SP44y8liFXqfsMGuywJ7mK2cHvnA==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/service-error-classification": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-middleware": "3.254.0", - "@aws-sdk/util-retry": "3.254.0", - "tslib": "^2.3.1", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/service-error-classification": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "@aws-sdk/util-retry": "3.303.0", + "tslib": "^2.5.0", "uuid": "^8.3.2" } }, "@aws-sdk/middleware-sdk-sts": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.254.0.tgz", - "integrity": "sha512-Y074nmTp07thuOI6GePv8IKdL/OvkO1tn2l7QvnwQa3Sy/HyNai1V3MVtq4hRi1dgDjheKPVHPE+TnOmF3w5uA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.303.0.tgz", + "integrity": "sha512-igp7htNCUPhVL9Q6rJSgcx3qy/P2l2KAiS0oozOTaTXt3h0LbOusSXtwyA7qvLYeRthnw6msVW+rVBAW3Vo+3g==", "optional": true, "requires": { - "@aws-sdk/middleware-signing": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-signing": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-serde": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.254.0.tgz", - "integrity": "sha512-YuItb2nlKADTBItcn68eA8amX4quuR1+0GyFRkwssKS/iTjbIk+3gJ2s1zxkUhlyozH3U38Jvvqd+W9+gNpYIg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.303.0.tgz", + "integrity": "sha512-mmZozwYKgUgXkJrLVqgIYoOQ8DfKZS3pBBT3ZxWzv5Hz5M3oRqFgfVYljkeDM2CTvBweHpqVRTWqPDMcZisucg==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-signing": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.254.0.tgz", - "integrity": "sha512-HMVGf+yANjlKCUMFZJU2PNzbI9hbCgL+IX/Y4DGuQW9cp7EgZOxQre1LBKpcCqqPVQ4toIdfNH/K8uM2fpO6dg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.303.0.tgz", + "integrity": "sha512-rrLQcS2wFsUGj9Kyx78LRgRS8jwiixz/Nyv06SmcKhP680sweETpQz/EA+wcVEVRXmUI6vs4NtqXz36dU0X8Nw==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/signature-v4": "3.254.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-middleware": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/signature-v4": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-middleware": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-stack": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.254.0.tgz", - "integrity": "sha512-yPWRnjeLC0lPAEQbiqbC3+hnqXZ+uCSoSevGndU5KWMMiXLxKZn7Y0B3kG8NAnNNuPid+wYFWWU9rKiBRvWR/w==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.303.0.tgz", + "integrity": "sha512-6KmdroXLexzILGxF/Xq0cGBs+B8Ipm1pff8qnWCT6KldYp+Q40bVcJrExkVHDN1uOsOxu20ixW2yujOKS356zg==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/middleware-user-agent": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.254.0.tgz", - "integrity": "sha512-hp5UYRg3ysZXMFMv34nYexyom6Z3pdx+OmisJz4w3AMigT8y57Ps30Vg+1QYaGlQkI4vfvcmdZX2Q+kp+mb9gQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.303.0.tgz", + "integrity": "sha512-ZVMVNxPRn2jXog3V4xWokSYoQxTKAdKlNoCfjqFplsF70r8sXfgZtOMF5ZhGo+Hgsx7GqpR/NWPKJtZD2nigpg==", "optional": true, "requires": { - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-endpoints": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/node-config-provider": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.254.0.tgz", - "integrity": "sha512-3Bp3Gp2NOY9gab738xf07TysO5iB0Ib9qRNGDlxX8SX8fZDRnxrF2cn+Tjte42wrO54orwhSyuTaIlAqKeii8Q==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.303.0.tgz", + "integrity": "sha512-Ywbo9+2SkbdmNgCoxYJrv+YrFDtBH7hHtn2ywtzP4t57d4t0V/LNrNQsrAsXxqy48OS5r2ovOLHiqJS5jp1oyw==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/node-http-handler": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.254.0.tgz", - "integrity": "sha512-DX2WJ3pub+3FF9GpoF5doERCn06MxS/UmmbKnIIokWQHjPZVomNh/1P3Cf9Jn9jeIPgh4UOg0uPD8cUm/cwHQw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.303.0.tgz", + "integrity": "sha512-5Te+mwBIOiQr2nM7/SNVFkvYHOH/CswOmUMV4Gxc7YjuervhrYvVFs2P+lL+c8rfiVMTLWjnJ6JiL2JdJfYgnQ==", "optional": true, "requires": { - "@aws-sdk/abort-controller": "3.254.0", - "@aws-sdk/protocol-http": "3.254.0", - "@aws-sdk/querystring-builder": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/abort-controller": "3.303.0", + "@aws-sdk/protocol-http": "3.303.0", + "@aws-sdk/querystring-builder": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/property-provider": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.254.0.tgz", - "integrity": "sha512-BLZF/LDFjAgv2ZY0vhThU58k++Aw+SK7qNU7XT0D84q5iWlYRKptQEvSSvIkBSI/rZoppOFhK7W80I8kNNbh+Q==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.303.0.tgz", + "integrity": "sha512-d1qbn0pCz+jvB0dcWMWuIlWYM8dWCg3185ngMgUQxkgUk7/kEbwGBsmT+xtZAMQcwcgPkSm8qeATEQ7ToiH8eQ==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/protocol-http": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.254.0.tgz", - "integrity": "sha512-4o/I/qhMUTp70njwWe3ttyRJSAKegnr8l3oVWAf1/q1ZHpcxbRRZEDvrkx4KSunFeXTTGHcff1oyLSRG/cKMsQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.303.0.tgz", + "integrity": "sha512-eqblSsdmKBzgNl06dUnL4toq/OQgZyxVsxHCz2nI/xBk5lI/qAZIJyEgP2GmP8aoWwneAq33roG0VLZoxQ8exg==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/querystring-builder": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.254.0.tgz", - "integrity": "sha512-Er+pOGTrPxelrzggibduO+eB1ClaU2BhjA8gd0nORS3kqktQggG3tKmRSIilegi9WOa3awCk6CnnuAf0pBrbUA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.303.0.tgz", + "integrity": "sha512-0eMp2gd7Ro0svJ6YVnp9cUiGtrc1d/HynyMfbDkLkqWJAnHMz7Oc1GjK5YyL1hdxm0W+JWZCPR0SovLiaboKDw==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-uri-escape": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/querystring-parser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.254.0.tgz", - "integrity": "sha512-WwRD99dwGo2aIrRjLHUAXaWCZ+3fj88IhIwciWTqrHBS3TQWXllOOQmYo7f+aMBB4Q1K6KdKITNi8L7aUuDv2g==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.303.0.tgz", + "integrity": "sha512-KNJSQiTFiA7W5eYCox8bLGM7kghC3Azad86HQhdsYO0jCoPxcgj8MeP6T7fPTIC4WcTwcWb7T1MpzoeBiKMOTQ==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/service-error-classification": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.254.0.tgz", - "integrity": "sha512-8GHqMJBBF9yoMBG/Nf9PusUSMFjG8ygps/cSJPlgcG2vbFn8BCdBZVc4ptXqICZUnBB/6lrxy8nCmNUaru48jg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.303.0.tgz", + "integrity": "sha512-eO13PzdtRO9C+g3tyFOpIblX2SbDrIbg2bNtB8JOfjVi3E1b5VsSTXXU/cKV+lbZ9XMzMn3VzGSvpo6AjzfpxA==", "optional": true }, "@aws-sdk/shared-ini-file-loader": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.254.0.tgz", - "integrity": "sha512-UH4YTXuG+q004vA+jNrVhrD5XQCIAgpL/eriObJnQpKUVef1mkkEDHZs8+8+ZPsk4p/iBrIJ3lXNf7iDA/BFzw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.303.0.tgz", + "integrity": "sha512-yI84mnnh3pdQtIOo+oGWofaI0rvfhp3DOavB8KHIkQr+RcjF+fxsqbelRfVb25gx7yEWPNCMB8wM+HhklSEFJg==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/signature-v4": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.254.0.tgz", - "integrity": "sha512-9FoEnipA9hAgEp6oqIT3+hobF+JgIXIn5QV8kAB7QGxEDqs/pdpEbGc9qbxi0ghdjvqzOSDir9gNI3w0cL8Aug==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.303.0.tgz", + "integrity": "sha512-muw5yclLOgXPHIxv60mhO6R0GVjKbf+M6E/cWvIEVGq8Ke+mLMYNFYNdKP/f/8JgTtW2xwQ7pIK3U8x284ZqPw==", "optional": true, "requires": { - "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.254.0", - "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.254.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/is-array-buffer": "3.303.0", + "@aws-sdk/types": "3.303.0", + "@aws-sdk/util-hex-encoding": "3.295.0", + "@aws-sdk/util-middleware": "3.303.0", + "@aws-sdk/util-uri-escape": "3.303.0", + "@aws-sdk/util-utf8": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/smithy-client": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.254.0.tgz", - "integrity": "sha512-SI0jz9JfWi1IaakDX/26xliKTIMJpzwwDoyQPEfZ/L0KKdpr2gNhljA3sR2pZ2EM1oqOaXpMHAunSzv7EBpBWg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.303.0.tgz", + "integrity": "sha512-WDTC9ODdpRAXo8+Mtr5hsPJeR3y3LxfZZFg5dplJgkaxV+MFdnsUCxZfAZMnxcGy5Q2qTzlLLNk9CpadS72v+g==", "optional": true, "requires": { - "@aws-sdk/middleware-stack": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/middleware-stack": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/token-providers": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.254.0.tgz", - "integrity": "sha512-i3W+YWrMtgdFPDWW/m56xrkBhqrB6beKgQi46oSM/aFZ3ZAkFJLfbsLK99LWzVxtzELTSFjJWY54r+Au/hb2kQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.303.0.tgz", + "integrity": "sha512-7G7VYbqyX0v6RTD/m7XmArZToMek4jYXR/TuuGHK6ifNJeMDwkU4BcoVDj37vvTPYp6qKU5IE+bE3XmPyVWnGQ==", "optional": true, "requires": { - "@aws-sdk/client-sso-oidc": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/shared-ini-file-loader": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sso-oidc": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/shared-ini-file-loader": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/types": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.254.0.tgz", - "integrity": "sha512-xDEDk6ZAGFO0URPgB6R2mvQANYlojHLjLC9zzOzl07F+uqYS30yZDIg4UFcqPt/x48v7mxlKZpbaZgYI2ZLgGA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.303.0.tgz", + "integrity": "sha512-H+Cy8JDTsK87MID6MJbV9ad5xdS9YvaLZSeveC2Zs1WNu2Rp6X9j+mg3EqDSmBKUQVAFRy2b+CSKkH3nnBMedw==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/url-parser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.254.0.tgz", - "integrity": "sha512-Za0JGUa9p5GQ8t2tVtKaRSjLUxrmEdnBlUiZ2zKm86wFxgQnjbMwzD3mvyJ5OaVsXScU5vzc3CXHIXSvS7h7Ng==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.303.0.tgz", + "integrity": "sha512-PXMXGhr89s0MiPTf8Ft/v3sPzh2geSrFhTVSO/01blfBQqtuu0JMqORhLheOdi16AhQNVlYHDW2tWdx7/T+KsA==", "optional": true, "requires": { - "@aws-sdk/querystring-parser": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/querystring-parser": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-base64": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", - "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.303.0.tgz", + "integrity": "sha512-oj+p/GHHPcZEKjiiOHU/CyNQeh8i+8dfMMzU+VGdoK5jHaVG8h2b+V7GPf7I4wDkG2ySCK5b5Jw5NUHwdTJ13Q==", "optional": true, "requires": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-body-length-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", - "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.303.0.tgz", + "integrity": "sha512-T643m0pKzgjAvPFy4W8zL+aszG3T22U8hb6stlMvT0z++Smv8QfIvkIkXjWyH2KlOt5GKliHwdOv8SAi0FSMJQ==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-body-length-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", - "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.303.0.tgz", + "integrity": "sha512-/hS8z6e18Le60hJr2TUIFoUjUiAsnQsuDn6DxX74GXhMOHeSwZDJ9jHF39quYkNMmAE37GrVH4MI9vE0pN27qw==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-buffer-from": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", - "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.303.0.tgz", + "integrity": "sha512-hUU+NW+SW6RNojtAKnnmz+tDShVKlEx2YsS4a5fSfrKRUes+zWz10cxVX0RQfysd3R6tdSHhbjsSj8eCIybheg==", "optional": true, "requires": { - "@aws-sdk/is-array-buffer": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/is-array-buffer": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-config-provider": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", - "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.295.0.tgz", + "integrity": "sha512-/5Dl1aV2yI8YQjqwmg4RTnl/E9NmNsx7HIwBZt+dTcOrM0LMUwczQBFFcLyqCj/qv5y+VsvLoAAA/OiBT7hb3w==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-defaults-mode-browser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.254.0.tgz", - "integrity": "sha512-vj/s+BuqNKTHN9bsZ/HY7vpBWbo3F+4c3/ZoKSZa5Jc7jAuGCbx3zWwHdJFDgvbqLvsTBw80Q9d/CDy9pKj/tQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.303.0.tgz", + "integrity": "sha512-jtZgCKelFe4/SHDHQu9ydbYttxSfqSlQojA5qxTJxLvzryIB+/GTHQ+sYWyMyzaD489W9elt1/cSsXd4LtPK0A==", "optional": true, "requires": { - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-defaults-mode-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.254.0.tgz", - "integrity": "sha512-gvD2+Uf60c2BgUYv2d6R4dSpO/CbvybqblgF8lKZCsHkDWzfEdPv9nlJgUWM1cuMKQ0hBZ3cL3ilOwVKRVPyiQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.303.0.tgz", + "integrity": "sha512-c86iyot/u9bCVcy/rlWL+0kdR51c7C2d2yDXvO9iFCdMKAs28Hw1ijGczVmOcUQ61zKNFSGYx+VekHXN9IWYOg==", "optional": true, "requires": { - "@aws-sdk/config-resolver": "3.254.0", - "@aws-sdk/credential-provider-imds": "3.254.0", - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/property-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.303.0", + "@aws-sdk/credential-provider-imds": "3.303.0", + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/property-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-endpoints": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.254.0.tgz", - "integrity": "sha512-BzBIOnhVrs4RFTpGZErZfAV1VhqWglxn047VYijmCQe8Aejq4mJAaepSwHYar++XC0+pduD5YO8IidW8z/1vQQ==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.303.0.tgz", + "integrity": "sha512-dPg9+l3VY3nclWFiWAVNWek5lQwgdtY8oRYOgCeyntce9FlNrPQgCRTVr36D0iQ0aNCs0GWzfjgL+rIdCF66/w==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-hex-encoding": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", - "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.295.0.tgz", + "integrity": "sha512-XJcoVo41kHzhe28PBm/rqt5mdCp8R6abwiW9ug1dA6FOoPUO8kBUxDv6xaOmA2hfRvd2ocFfBXaUCBqUowkGcQ==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-locate-window": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", - "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", + "version": "3.295.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.295.0.tgz", + "integrity": "sha512-d/s+zhUx5Kh4l/ecMP/TBjzp1GR/g89Q4nWH6+wH5WgdHsK+LG+vmsk6mVNuP/8wsCofYG4NBqp5Ulbztbm9QA==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-middleware": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.254.0.tgz", - "integrity": "sha512-gn7vInNTRBo2QatOB+uU99JwV53wf/zlTUnUK0qOuebtSDLMdiO+msiMi2ctz9vMIrtc2XMXNQro1aE0aUPy4w==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.303.0.tgz", + "integrity": "sha512-HAfBcbZw1+pY3dIEDM4jVpH1ViFcGH5s0q1dr+x4rcLGpMM3B4dH0HUgDPtycG8sw+nk+9jGgiEtgaCNOpJLGA==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-retry": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.254.0.tgz", - "integrity": "sha512-IVA4wAOJpVssEIbJmeq1fdDYvrkOqYFK9Pz4tERmMz33003fyY92dU468Lulw8MnsSALYiwWUoWSFg9L5RCTug==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.303.0.tgz", + "integrity": "sha512-RWwRNjoWMcpDouz69wPuFXWFVzwYtUkTbJfa46SjKl1IwqMHS4f9yjJfCwJIoLOW9M/o2JB7nD0Ij3gqqzajLw==", "optional": true, "requires": { - "@aws-sdk/service-error-classification": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/service-error-classification": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.303.0.tgz", + "integrity": "sha512-N3ULNuHCL3QzAlCTY+XRRkRQTYCTU8RRuzFCJX0pDpz9t2K+tLT7DbxqupWGNFGl5Xlulf1Is14J3BP/Dx91rA==", "optional": true, "requires": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.254.0.tgz", - "integrity": "sha512-2HvwH8l7ln4qTDsU3rgH9NvSSo5qhX+2Lenb6XvNnIMkL4r/tPhNIaGKtoQRfpzLH378Mm9XEQnJM5UXFRWuTA==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.303.0.tgz", + "integrity": "sha512-Kex3abpUrTX9z129jiI8sfjIUmQDwiWjhkvBkPmrwjFY/sZcnOcXj5nP2iwJ+k6CnA5ZK5PjZ6P62t+eJ5MTXw==", "optional": true, "requires": { - "@aws-sdk/types": "3.254.0", + "@aws-sdk/types": "3.303.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "@aws-sdk/util-user-agent-node": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.254.0.tgz", - "integrity": "sha512-6nc9bmRP+2JqbBJ5oRZZRU8l35X3VcWF5j8XvmamWjIABsanc6Gv6NV4qAa3imPjIyWNiShZn/YkTBYs1exsdg==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.303.0.tgz", + "integrity": "sha512-QYUg8F/Ho6AsVZaSSRMf/LWoEPDyOwgKZBw3AbKoH6RxAdAsdL1SXz5t4A6jHakP9TLVN2Yw2WRbHDe4LATASQ==", "optional": true, "requires": { - "@aws-sdk/node-config-provider": "3.254.0", - "@aws-sdk/types": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.303.0", + "@aws-sdk/types": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-utf8": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", - "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", + "version": "3.303.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.303.0.tgz", + "integrity": "sha512-tZXVuMOIONPOuOGBs/XRdzxv6jUvTM620dRFFIHZwlGiW8bo0x0LlonrzDAJZA4e9ZwmxJIj8Ji13WVRBGvZWg==", "optional": true, "requires": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.303.0", + "tslib": "^2.5.0" } }, "@aws-sdk/util-utf8-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", - "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", - "optional": true, - "requires": { - "tslib": "^2.3.1" - } - }, - "@aws-sdk/util-utf8-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz", - "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==", + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "optional": true, "requires": { - "@aws-sdk/util-buffer-from": "3.208.0", "tslib": "^2.3.1" } }, @@ -15591,23 +15612,23 @@ } }, "@mikro-orm/core": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.7.tgz", - "integrity": "sha512-XLCY3S10nz7uUFGTFypUuaO48cQAhCUiSsQBxpqqhlOqVz6yxGYdqAe7SmMKuplmpOoVL8OGDZrULzd0bIjtYA==", + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.15.tgz", + "integrity": "sha512-LcyHVEW6RO6o1ZA1mVdNypsdx5uGkV19LkKNv4GS88cJxTXzYuuvthv3qZNbrTfDqJojgf+dcI3wQn/9Pbi0Cw==", "requires": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", "fs-extra": "11.1.0", "globby": "11.1.0", - "mikro-orm": "~5.6.7", + "mikro-orm": "~5.6.15", "reflect-metadata": "0.1.13" } }, "@mikro-orm/mongodb": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.7.tgz", - "integrity": "sha512-aRrh9IKiEARKrG1RI87kc1rKWnQChMCkpte21YJhKPUjGlEhnGIkB/yY34edXfhxYvjQPmkhcDXgOe58oOk6WA==", + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.15.tgz", + "integrity": "sha512-nBHVWLP67ZOVyTlQV+Mvf0RIyi75vfJkU6jsKRSTr8vhnQTizkMxYwyTYUyt1GzlJwajqgwpxQEf57w9SXpE3Q==", "requires": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -16413,6 +16434,39 @@ "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.19.0" + }, + "dependencies": { + "@reldens/storage": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", + "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "requires": { + "@mikro-orm/core": "^5.6.7", + "@mikro-orm/mongodb": "^5.6.7", + "@reldens/utils": "^0.18.0", + "knex": "^2.4.1", + "mysql": "^2.18.1", + "objection": "^3.0.1" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } + } + }, + "@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/modifiers": { @@ -16421,6 +16475,16 @@ "integrity": "sha512-tJyHi7NIOpANh+8lBmq7GpD5xu6L2tGutvfd8+RaliFy5BMXzwBM7ce9+tKd3Ja5diUi5WMA8w9xKT5lZ+327w==", "requires": { "@reldens/utils": "^0.19.0" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/skills": { @@ -16431,25 +16495,58 @@ "@reldens/modifiers": "^0.19.0", "@reldens/storage": "^0.13.0", "@reldens/utils": "^0.19.0" + }, + "dependencies": { + "@reldens/storage": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", + "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "requires": { + "@mikro-orm/core": "^5.6.7", + "@mikro-orm/mongodb": "^5.6.7", + "@reldens/utils": "^0.18.0", + "knex": "^2.4.1", + "mysql": "^2.18.1", + "objection": "^3.0.1" + }, + "dependencies": { + "@reldens/utils": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", + "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } + } + }, + "@reldens/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "requires": { + "await-event-emitter": "^2.0.2" + } + } } }, "@reldens/storage": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.13.0.tgz", - "integrity": "sha512-b9iuTk+szM7I9RGEcXuhK295EBhlqQ1K8LjUCvdJwsEHrxuz14EYCMZppykg8FEh1LYO1LCWQrOuywOPf7oEtg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.14.0.tgz", + "integrity": "sha512-z87VGmJiHO6kKCYIMxeZWcIgZzisw1DINfDq9unlh5ewO4rIgabOdcl1LKBOQpf0TJ3EesVD77C1eJu8B6weHg==", "requires": { - "@mikro-orm/core": "^5.6.7", - "@mikro-orm/mongodb": "^5.6.7", - "@reldens/utils": "^0.18.0", - "knex": "^2.4.1", + "@mikro-orm/core": "^5.6.15", + "@mikro-orm/mongodb": "^5.6.15", + "@reldens/utils": "^0.19.0", + "knex": "^2.4.2", "mysql": "^2.18.1", "objection": "^3.0.1" }, "dependencies": { "@reldens/utils": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.18.1.tgz", - "integrity": "sha512-ysWT+grnzdkrISMFsXvMBOMhknoIp9EvKkDMuUSB0zl+KzeEULqGcMz1B0MBXPbULVNz70DBExqVNKxFJUXWvg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", "requires": { "await-event-emitter": "^2.0.2" } @@ -16457,9 +16554,9 @@ } }, "@reldens/utils": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", - "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.20.0.tgz", + "integrity": "sha512-WzipJU4yoH68mVWh1+oTEI7ZVoxRTLli81ZI74v1KK16zihFG3YoJ5TIIQg+7BJMTjEkUXj3+PoEGRTON8KjLQ==", "requires": { "await-event-emitter": "^2.0.2" } @@ -18184,9 +18281,9 @@ "optional": true }, "fast-xml-parser": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", - "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", + "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", "optional": true, "requires": { "strnum": "^1.0.5" @@ -19137,9 +19234,9 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knex": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", - "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz", + "integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==", "requires": { "colorette": "2.0.19", "commander": "^9.1.0", @@ -19398,9 +19495,9 @@ } }, "mikro-orm": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.7.tgz", - "integrity": "sha512-mxkg+BDXmuQh4PYlWkv0Yysy33Yvg/31BZN4UWWZpV9gcLMYPQcaEF8qfSW7sMbrdNSJcvqf2MX6etmIARHJ9A==" + "version": "5.6.15", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.15.tgz", + "integrity": "sha512-DlErpPXJ1gOqbDWgRXq5yEuhuOZP5QXC9EoP8n0OL8klB/Nm/qNRN0ATe0+9TyYdQHNe8BdpPMTwDee56VzdVA==" }, "mime": { "version": "3.0.0", @@ -21365,9 +21462,9 @@ } }, "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "type-fest": { "version": "0.20.2", diff --git a/package.json b/package.json index 046a0826e..f10dd9c1c 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "@reldens/items-system": "^0.18.1", "@reldens/modifiers": "^0.19.0", "@reldens/skills": "^0.18.0", - "@reldens/storage": "^0.13.0", - "@reldens/utils": "^0.19.0", + "@reldens/storage": "^0.14.0", + "@reldens/utils": "^0.20.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", "colyseus": "0.14.24", From a9d5d0e60b3688a05a4fe0ab4867aaba59075af2 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 7 Apr 2023 20:29:08 +0200 Subject: [PATCH 39/82] - Reldens - v4.0.0 - Clan WIP. - Migrations fix. --- lib/teams/client/clan-message-handler.js | 21 ++++++++++++------ lib/teams/client/target-box-enricher.js | 15 ++++++++++--- lib/teams/server/clan-factory.js | 2 ++ lib/teams/server/clan-updates-handler.js | 3 ++- .../create-player-clan-handler.js | 7 ++---- .../server/message-actions/clan-create.js | 14 ++++++++---- .../server/message-actions/clan-leave.js | 7 +++--- .../server/message-actions/try-clan-invite.js | 12 +++++++--- lib/users/server/player.js | 22 +++++++++++++++++++ migrations/production/beta.26-sql-update.sql | 19 ++++++++-------- 10 files changed, 87 insertions(+), 35 deletions(-) diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 817062bc8..5396c9f72 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -84,7 +84,8 @@ class ClanMessageHandler } this.roomEvents.uiSetTitle(uiBox, {title}); this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); - this.updateClanBox({}, container); + this.updateClanBox(container); + this.setClanFromMessage(); } showClanRequest() @@ -122,14 +123,20 @@ class ClanMessageHandler let uiBox = this.uiScene.elementsUi[TeamsConst.CLAN_KEY]; this.roomEvents.uiSetTitle(uiBox, {title}); this.roomEvents.uiSetContent(uiBox, {content: ''}, this.uiScene); + this.setClanFromMessage(); + this.updateClanBox(container); + } + + setClanFromMessage() + { let players = sc.get(this.message, 'players', false); this.uiScene.currentClan = { id: this.message.id, name: this.message.clanName, leader: this.message.leaderName, + ownerId: this.message.ownerId, players }; - this.updateClanBox(players, container); } removeClanUi() @@ -207,14 +214,14 @@ class ClanMessageHandler ); this.gameManager.activeRoomEvents.room.send('*', { act: TeamsConst.ACTIONS.CLAN_CREATE, - [TeamsConst.ACTIONS.CLAN_NAME]: nameInput.value, - id: this.message.id + [TeamsConst.ACTIONS.CLAN_NAME]: nameInput.value }); }); } - updateClanBox(players, container) + updateClanBox(container) { + let players = sc.get(this.message, 'players', false); if(!players){ Logger.error('Players not defined for clan box update.'); return; @@ -236,7 +243,7 @@ class ClanMessageHandler Logger.error('Missing template "clanContainer".'); return ''; } - let isPlayerOwner = this.gameManager.getCurrentPlayer().playerId === this.message.id; + let isPlayerOwner = this.gameManager.playerData.id === this.message.ownerId; let leaveActionLabel = isPlayerOwner ? this.gameManager.config.getWithoutLogs('client/clan/labels/disbandLabel', TeamsConst.LABELS.CLAN.DISBAND) : this.gameManager.config.getWithoutLogs('client/clan/labels/leaveLabel', TeamsConst.LABELS.CLAN.LEAVE); @@ -269,7 +276,7 @@ class ClanMessageHandler Logger.error('Missing template "clanPlayerData".'); return ''; } - let isPlayerOwner = playerData.id === this.message.id; + let isPlayerOwner = playerData.id === this.message.ownerId; return this.gameManager.gameEngine.parseTemplate(templateContent, { playerId: playerData.id, playerName: playerData.name, diff --git a/lib/teams/client/target-box-enricher.js b/lib/teams/client/target-box-enricher.js index c5bea978f..1ea85a580 100644 --- a/lib/teams/client/target-box-enricher.js +++ b/lib/teams/client/target-box-enricher.js @@ -13,14 +13,23 @@ class TargetBoxEnricher static appendClanInviteButton(gameManager, target, previousTarget, targetName) { - let currentPlayer = gameManager.getCurrentPlayer(); - if(!currentPlayer.currentClan){ + let currentClan = gameManager?.gameEngine?.uiScene?.currentClan; + if(!currentClan){ + // current player has none clan: + return false; + } + if(currentClan.players[target.id]){ + // target player is already on the clan: return false; } + let currentPlayer = gameManager.getCurrentPlayer(); if(!this.targetIsValidPlayer(target, currentPlayer)){ + // target is not the current player return false; } - if(currentPlayer.currentClan.players[target.id]){ + let isOpenInvites = gameManager.config.getWithoutLogs('client/clan/general/openInvites', false); + if(gameManager.playerData.id !== currentClan.ownerId && !isOpenInvites){ + // only clan owner can invite: return false; } return this.appendInviteButton('clan', target, gameManager, targetName); diff --git a/lib/teams/server/clan-factory.js b/lib/teams/server/clan-factory.js index 99d06e8f8..575858fa5 100644 --- a/lib/teams/server/clan-factory.js +++ b/lib/teams/server/clan-factory.js @@ -12,6 +12,7 @@ class ClanFactory static async create(clanId, playerOwner, clientOwner, sharedProperties, teamsPlugin) { + // @TODO - BETA - Refactor to extract the teamsPlugin. let clanModel = await teamsPlugin.dataServer.getEntity('clan').loadByIdWithRelations( clanId, ['members.parent_player, parent_level.modifiers'] @@ -26,6 +27,7 @@ class ClanFactory ownerClient: clientOwner, sharedProperties }); + return teamsPlugin.clans[clanId]; } } diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 070c46e94..70bef967e 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -26,7 +26,8 @@ class ClanUpdatesHandler delete otherPlayersData[i]; let sendUpdate = { act: TeamsConst.ACTIONS.CLAN_UPDATE, - id: clan.ownerClient.id, + id: clan.id, + ownerId: clan.owner.player_id, listener: TeamsConst.CLAN_KEY, players: otherPlayersData, leaderName: clan.owner.playerName, diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 9c17f9e5e..86c4a45fe 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -33,14 +33,11 @@ class CreatePlayerClanHandler } let clan = await this.loadClan(teamsPlugin, clanMemberModel.clan_id, playerSchema, client, room); if(!clan){ - Logger.error('Loading clan by ID "'+clanMemberModel.clan_id+'" not found.'); + Logger.error('Loading clan by ID "'+clanMemberModel.clan_id+'" not found.', clan); return false; } clan.join(playerSchema, client); - if(!playerSchema.privateData){ - playerSchema.privateData = {}; - } - playerSchema.privateData.clan = clanMemberModel.clan_id; + playerSchema.setPrivate('clan', clanMemberModel.clan_id); let endEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClanUpdate', endEvent); if(!endEvent.continueProcess){ diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index 1306e6c43..e7d44aa79 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -50,7 +50,7 @@ class ClanCreate Logger.info('Clan owner creation error "'+clanName+'".', createdClanModel, ownerMember); return this.clanCreateSend(client, TeamsConst.VALIDATION.CREATE_OWNER_ERROR, clanName); } - playerSchema.privateData.clan = createdClanModel.id; + playerSchema.setPrivate('clan', createdClanModel.id); await ClanFactory.create( createdClanModel.id, playerSchema, @@ -58,7 +58,13 @@ class ClanCreate room.config.get('client/ui/teams/sharedProperties'), teamsPlugin ); - return this.clanCreateSend(client, TeamsConst.VALIDATION.SUCCESS, clanName, playerSchema.player_id); + return this.clanCreateSend( + client, + TeamsConst.VALIDATION.SUCCESS, + clanName, + playerSchema.player_id, + createdClanModel.id + ); } static async fetchInitialLevel(teamsPlugin) @@ -82,10 +88,10 @@ class ClanCreate result }; if(ownerId){ - sendData.id = ownerId; + sendData.ownerId = ownerId; } if(clanId){ - sendData.clanId = clanId; + sendData.id = clanId; } client.send('*', sendData); return true; diff --git a/lib/teams/server/message-actions/clan-leave.js b/lib/teams/server/message-actions/clan-leave.js index 4cc259746..1f8b1f3f3 100644 --- a/lib/teams/server/message-actions/clan-leave.js +++ b/lib/teams/server/message-actions/clan-leave.js @@ -37,9 +37,9 @@ class ClanLeave let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ let sendUpdate = { - act: TeamsConst.ACTIONS.CLAN_LEFT, + act: TeamsConst.ACTIONS.CLAN_INITIALIZE, id: currentClan.ownerClient.id, - listener: TeamsConst.KEY + listener: TeamsConst.CLAN_KEY }; await teamsPlugin.events.emit('reldens.clanLeaveBeforeSendUpdate', { playerId, @@ -60,7 +60,8 @@ class ClanLeave return false; } delete teamsPlugin.clans[clanId]; - return teamsPlugin.dataServer.getEntity('clan').deleteById(clanId); + await teamsPlugin.dataServer.getEntity('clan').deleteById(clanId); + return true; } let event = {singleRemoveId, playerSchema, teamsPlugin, continueLeave: true}; await teamsPlugin.events.emit('reldens.clanLeaveAfterSendUpdate', event); diff --git a/lib/teams/server/message-actions/try-clan-invite.js b/lib/teams/server/message-actions/try-clan-invite.js index 7c421b86e..eb6e523ac 100644 --- a/lib/teams/server/message-actions/try-clan-invite.js +++ b/lib/teams/server/message-actions/try-clan-invite.js @@ -16,16 +16,22 @@ class TryClanInvite Logger.info('The player is trying to clan up with himself.', playerSchema.sessionId, data); return false; } + let playerClan = playerSchema.getPrivate('clan'); + if(!playerClan){ + Logger.warning('Player without a clan is trying to send a clan invite.', playerSchema.sessionId, data); + return false; + } let toPlayer = sc.get(room.activePlayers, data.id, false); if(false === toPlayer){ Logger.error('Player not found.', toPlayer, data); return false; } let sendData = { - act: TeamsConst.ACTIONS.TEAM_INVITE, - listener: TeamsConst.KEY, + act: TeamsConst.ACTIONS.CLAN_INVITE, + listener: TeamsConst.CLAN_KEY, from: playerSchema.playerName, - id: playerSchema.sessionId, + id: playerClan.id, + ownerId: playerSchema.sessionId }; let event = {client, data, room, playerSchema, teamsPlugin, continueStart: true}; await teamsPlugin.events.emit('reldens.tryClanStart', event); diff --git a/lib/users/server/player.js b/lib/users/server/player.js index 8805c19fb..877d657e1 100644 --- a/lib/users/server/player.js +++ b/lib/users/server/player.js @@ -20,6 +20,7 @@ class Player extends Schema super(); try { let player = data.player; + // @TODO - BETA - Replace "id" to be "player_id" and set the ID from the model as userId. this.id = data.id; // this is the user_id from the storage // @TODO - BETA - Use camelCase on player_id and role_id. this.player_id = player.id; // this is the player_id from the storage @@ -49,6 +50,27 @@ class Player extends Schema } } + // @TODO - BETA - Enclose a single entity that would contain the model, the schema, the client, the extra data, etc. + getPrivate(key) + { + return this.privateData[key] || null; + } + + setPrivate(key, data) + { + return this.privateData[key] = data; + } + + getCustom(key) + { + return this.customData[key] || null; + } + + setCustom(key, data) + { + return this.customData[key] = data; + } + getPosition() { return { diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 611e4dac0..a1169d200 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -32,13 +32,13 @@ UPDATE `config` SET `type` = @json_id WHERE `type` = 'j'; UPDATE `config` SET `type` = @comma_separated_id WHERE `type` = 'c'; ALTER TABLE `config` CHANGE COLUMN `type` `type` INT UNSIGNED NOT NULL COLLATE 'utf8_unicode_ci' AFTER `value`; -ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; +ALTER TABLE `config` ADD CONSTRAINT `FK_config_config_types` FOREIGN KEY (`type`) REFERENCES `config_types` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +ALTER TABLE `config` ADD UNIQUE INDEX `scope_path` (`scope`, `path`); # Config: INSERT INTO `config` VALUES (NULL, 'client', 'general/gameEngine/updateGameSizeTimeOut', '500', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/options/acceptOrDecline', '{"1":{"label":"Accept","value":1},"2":{"label":"Decline","value":2}}', @json_id); INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); -INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/requestFromTitle', 'Team request from:', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/leaderNameTitle', 'Team leader: %leaderName', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'team/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/enabled', '1', @boolean_id); @@ -47,7 +47,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); -INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/requestFromTitle', 'Clan request from:', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/general/openInvites', '1', @boolean_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/requestFromTitle', 'Clan request from:', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/clanTitle', 'Clan: %clanName - Leader: %leaderName', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); @@ -64,6 +64,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'rewards/titles/rewardMessage', 'Yo # Features: INSERT INTO `features` VALUES (NULL, 'teams', 'Teams', 1); +INSERT INTO `features` VALUES (NULL, 'rewards', 'Rewards', 1); # Clan and members: CREATE TABLE `clan` ( @@ -85,9 +86,13 @@ CREATE TABLE `clan_members` ( `clan_id` INT(10) UNSIGNED NOT NULL, `player_id` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `clan_id_player_id` (`clan_id`, `player_id`) USING BTREE, + UNIQUE INDEX `player_id` (`player_id`) USING BTREE, INDEX `FK__clan` (`clan_id`) USING BTREE, - INDEX `FK__players` (`player_id`) USING BTREE -) COLLATE='utf8_unicode_ci' ENGINE=InnoDB; + INDEX `FK__players` (`player_id`) USING BTREE, + CONSTRAINT `FK_clan_members_clan` FOREIGN KEY (`clan_id`) REFERENCES `clan` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT `FK_clan_members_players` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION +) COLLATE='utf8mb3_unicode_ci' ENGINE=InnoDB; CREATE TABLE `clan_levels` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -144,10 +149,6 @@ ALTER TABLE `items_item` ADD CONSTRAINT `FK_items_item_items_group` FOREIGN KEY ALTER TABLE `items_item_modifiers` ADD CONSTRAINT `FK_items_item_modifiers_items_item` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; ALTER TABLE `objects_items_inventory` ADD CONSTRAINT `FK_objects_items_inventory_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items_item` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION; - -# Features: -INSERT INTO `features` VALUES (NULL, 'rewards', 'Rewards', 1); - # Rewards: CREATE TABLE `rewards_modifiers` ( `id` int unsigned NOT NULL AUTO_INCREMENT, From c55c51cbdde6d2c4374930c47272e1d12cf5c902 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 12 Apr 2023 07:37:26 +0200 Subject: [PATCH 40/82] - Reldens - v4.0.0 - Clan invites WIP. --- lib/game/client/manager.js | 2 - lib/game/client/scene-dynamic.js | 13 +++-- lib/teams/client/clan-message-handler.js | 37 ++++++++---- lib/teams/server/clan-members-data-mapper.js | 27 --------- lib/teams/server/clan-updates-handler.js | 17 +++--- lib/teams/server/clan.js | 25 +++++++-- lib/teams/server/message-actions/clan-join.js | 56 ++++++------------- .../server/message-actions/clan-leave.js | 4 +- .../server/message-actions/try-clan-invite.js | 16 +++++- lib/teams/server/team.js | 14 ++++- 10 files changed, 107 insertions(+), 104 deletions(-) delete mode 100644 lib/teams/server/clan-members-data-mapper.js diff --git a/lib/game/client/manager.js b/lib/game/client/manager.js index 0c0f60863..66609e029 100644 --- a/lib/game/client/manager.js +++ b/lib/game/client/manager.js @@ -2,8 +2,6 @@ * * Reldens - GameManager * - * Like the ServerManager class, this is the main one that will handle everything on the client side. - * */ const { GameClient } = require('./game-client'); diff --git a/lib/game/client/scene-dynamic.js b/lib/game/client/scene-dynamic.js index 37aa45109..5ab395c67 100644 --- a/lib/game/client/scene-dynamic.js +++ b/lib/game/client/scene-dynamic.js @@ -166,13 +166,18 @@ class SceneDynamic extends Scene let keys = this.availableControllersKeyCodes(); let inputElements = this.gameManager.gameDom.getElements('input'); for(let inputElement of inputElements){ - this.loopKeysAddListenerToElement(keys, inputElement, 'focusin', 'removeCapture'); - this.loopKeysAddListenerToElement(keys, inputElement, 'click', 'removeCapture'); - this.loopKeysAddListenerToElement(keys, inputElement, 'focusout', 'addCapture'); - this.loopKeysAddListenerToElement(keys, inputElement, 'blur', 'addCapture'); + this.addAndRemoveCapture(keys, inputElement); } } + addAndRemoveCapture(keys, inputElement) + { + this.loopKeysAddListenerToElement(keys, inputElement, 'focusin', 'removeCapture'); + this.loopKeysAddListenerToElement(keys, inputElement, 'click', 'removeCapture'); + this.loopKeysAddListenerToElement(keys, inputElement, 'focusout', 'addCapture'); + this.loopKeysAddListenerToElement(keys, inputElement, 'blur', 'addCapture'); + } + availableControllersKeyCodes() { return [ diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 5396c9f72..0ad1b8bda 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -62,6 +62,7 @@ class ClanMessageHandler this.roomEvents.uiSetTitle(uiBox, {title}); this.roomEvents.uiSetContent(uiBox, {content: this.createClanContent()}, this.uiScene); this.activateCreateButton(); + this.addAndRemoveCaptureKeys(); } showNewClan() @@ -221,21 +222,25 @@ class ClanMessageHandler updateClanBox(container) { - let players = sc.get(this.message, 'players', false); - if(!players){ - Logger.error('Players not defined for clan box update.'); - return; - } - let teamMembers = ''; + let players = sc.get(this.message, 'players', []); + let clanMembers = ''; for(let i of Object.keys(players)){ - teamMembers += this.createClanMemberBox(players[i]); + clanMembers += this.createClanMemberBox(players[i]); } - container.innerHTML = this.createClanContainer(teamMembers); + container.innerHTML = this.createClanContainer(clanMembers); this.activateClanTargetActions(players); this.activateClanLeaveButtonAction(); } - createClanContainer(teamMembers) + addAndRemoveCaptureKeys() + { + let dynamicScene = this.gameManager.getActiveScene(); + let keys = dynamicScene.availableControllersKeyCodes(); + let inputElement = this.gameManager.gameDom.getElement('.clan-name-input'); + dynamicScene.addAndRemoveCapture(keys, inputElement); + } + + createClanContainer(clanMembers) { // @TODO - BETA - Move the template load from cache as part of the engine driver. let templateContent = this.uiScene.cache.html.get('clanContainer'); @@ -251,7 +256,7 @@ class ClanMessageHandler clanId: this.message.id, playerId: this.gameManager.getCurrentPlayer().playerId, leaveActionLabel: leaveActionLabel, - teamMembers + clanMembers }; return this.gameManager.gameEngine.parseTemplate(templateContent, templateParams); } @@ -330,10 +335,18 @@ class ClanMessageHandler let playerData = playersData[i]; let selectorPlayerName = '.clan-player-'+i+' .player-name'; let selectorPlayerProperties = '.clan-player-'+i+' .properties-list-container'; - this.gameDom.getElement(selectorPlayerName).addEventListener('click', () => { + let playerNameElement = this.gameDom.getElement(selectorPlayerName); + if(!playerNameElement){ + Logger.notice('Player name element not found.', selectorPlayerName); + } + playerNameElement?.addEventListener('click', () => { this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); - this.gameDom.getElement(selectorPlayerProperties).addEventListener('click', () => { + let playerPropertiesElement = this.gameDom.getElement(selectorPlayerProperties); + if(!playerNameElement){ + Logger.notice('Player properties element not found.', selectorPlayerProperties); + } + playerPropertiesElement?.addEventListener('click', () => { this.gameManager.getCurrentPlayer().setTargetPlayerById(playerData.sessionId); }); } diff --git a/lib/teams/server/clan-members-data-mapper.js b/lib/teams/server/clan-members-data-mapper.js deleted file mode 100644 index 004a5d625..000000000 --- a/lib/teams/server/clan-members-data-mapper.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * - * Reldens - ClanMembersDataMapper - * - */ - -class ClanMembersDataMapper -{ - - static fetchPlayersData(clan) - { - let teamPlayersId = Object.keys(clan.members); - let membersData = {}; - for(let i of teamPlayersId){ - membersData[i] = { - name: clan.members[i].playerName, - level: clan.members[i].level, - id: clan.members[i].player_id, - sessionId: clan.members[i].sessionId, - }; - } - return membersData; - } - -} - -module.exports.ClanMembersDataMapper = ClanMembersDataMapper; diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 70bef967e..c3c18b724 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -24,15 +24,14 @@ class ClanUpdatesHandler for(let i of clientsKeys){ let otherPlayersData = Object.assign({}, playersList); delete otherPlayersData[i]; - let sendUpdate = { - act: TeamsConst.ACTIONS.CLAN_UPDATE, - id: clan.id, - ownerId: clan.owner.player_id, - listener: TeamsConst.CLAN_KEY, - players: otherPlayersData, - leaderName: clan.owner.playerName, - clanName: clan.name - }; + let sendUpdate = Object.assign( + { + act: TeamsConst.ACTIONS.CLAN_UPDATE, + listener: TeamsConst.CLAN_KEY, + players: otherPlayersData, + }, + clan.toArray() + ); clan.clients[i].send('*', sendUpdate); } return true; diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index e9b1d23dd..0a43c8f06 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -5,7 +5,7 @@ */ const { Team } = require('./team'); -const { sc } = require('@reldens/utils'); +const { Logger, sc } = require('@reldens/utils'); class Clan extends Team { @@ -16,6 +16,7 @@ class Clan extends Team this.name = sc.get(props, 'name', ''); this.points = sc.get(props, 'points', ''); this.members = sc.get(props, 'members', {}); + this.pendingInvites = sc.get(props, 'pendingInvites', {}); } static fromModel(props) @@ -49,10 +50,12 @@ class Clan extends Team toArray() { return { - name: this.name, + id: this.id, + clanName: this.name, points: this.points, level: this.level, - ownerId: this.owner.id, + ownerId: this.owner.player_id, + leaderName: this.owner.playerName, members: this.mapMembersToArray() }; } @@ -67,13 +70,27 @@ class Clan extends Team let member = this.members[i]; members[i] = { // @TODO - BETA - Make other members data optional, like level or class path. - name: member.parent_player.name, + name: member?.parent_player?.name, id: i } } return members; } + join(playerSchema, client) + { + let result = super.join(playerSchema, client); + if(!result){ + Logger.error('Player could not join the clan.', playerSchema.id, result, this.players); + return false; + } + this.members[playerSchema.id] = { + id: playerSchema.id, + name: playerSchema.playerName + } + return result; + } + } module.exports.Clan = Clan; \ No newline at end of file diff --git a/lib/teams/server/message-actions/clan-join.js b/lib/teams/server/message-actions/clan-join.js index 7c1848fc7..ac9119802 100644 --- a/lib/teams/server/message-actions/clan-join.js +++ b/lib/teams/server/message-actions/clan-join.js @@ -13,60 +13,36 @@ class ClanJoin static async execute(client, data, room, playerSchema, teamsPlugin) { - if(playerSchema.sessionId === data.id){ - Logger.info('The player is trying to join a clan with himself.', playerSchema.sessionId, data); + let clanToJoin = teamsPlugin.clans[data.id]; + if(!clanToJoin?.pendingInvites[playerSchema.sessionId]){ + Logger.error('Player trying to join a clan without invite.'); return false; } - let previousClanId = playerSchema?.privateData?.clan; + let previousClanId = playerSchema.getPrivate('clan'); if(previousClanId){ teamsPlugin.clans[previousClanId]?.leave(playerSchema); } - let clanOwnerPlayerSchema = room.playerByIdFromState(data.id); - if(!clanOwnerPlayerSchema){ - Logger.error('Player clan owner not found.', clanOwnerPlayerSchema, data); - return false; - } - let clanId = clanOwnerPlayerSchema?.privateData?.clan; - if(!clanId){ - Logger.error('Clan not found in player owner.', clanOwnerPlayerSchema, data); - return false; - } - let clanOwnerActivePlayer = room.activePlayers[data.id]; - if(!clanOwnerActivePlayer){ - Logger.error('Active player clan owner client not found.', clanOwnerActivePlayer, data); - return false; - } - let clanProps = { - owner: clanOwnerActivePlayer, - ownerClient: clanOwnerActivePlayer.client, - sharedProperties: room.config.get('client/ui/clans/sharedProperties') - }; - let currentClan = teamsPlugin.clans[clanId]; - if(!currentClan){ - let beforeCreateEvent = {clanProps, teamsPlugin, continueBeforeCreate: true}; - await teamsPlugin.events.emit('reldens.beforeClanCreate', beforeCreateEvent); - if(!beforeCreateEvent.continueBeforeCreate){ - return false; - } - currentClan = new Clan(clanProps); - } - let eventBeforeJoin = {currentClan, teamsPlugin, continueBeforeJoin: true}; + let eventBeforeJoin = {clanToJoin, teamsPlugin, continueBeforeJoin: true}; await teamsPlugin.events.emit('reldens.beforeClanJoin', eventBeforeJoin); if(!eventBeforeJoin.continueBeforeJoin){ return false; } - currentClan.join(playerSchema, client); - if(!playerSchema.privateData){ - playerSchema.privateData = {}; + let result = await teamsPlugin.dataServer.getEntity('clanMembers').create({ + player_id: playerSchema.id, + clan_id: clanToJoin.id + }); + if(!result){ + Logger.critical('Clan member could not be created.', playerSchema?.id, clanToJoin?.id); + return false; } - playerSchema.privateData.clan = currentClan.id; - teamsPlugin.clans[currentClan.id] = currentClan; - let eventBeforeJoinUpdate = {currentClan, teamsPlugin, continueBeforeJoinUpdate: true}; + clanToJoin.join(playerSchema, client); + playerSchema.setPrivate('clan', clanToJoin.id); + let eventBeforeJoinUpdate = {clanToJoin, teamsPlugin, continueBeforeJoinUpdate: true}; await teamsPlugin.events.emit('reldens.beforeClanUpdatePlayers', eventBeforeJoinUpdate); if(!eventBeforeJoinUpdate.continueBeforeJoinUpdate){ return false; } - return ClanUpdatesHandler.updateClanPlayers(currentClan); + return ClanUpdatesHandler.updateClanPlayers(clanToJoin); } } diff --git a/lib/teams/server/message-actions/clan-leave.js b/lib/teams/server/message-actions/clan-leave.js index 1f8b1f3f3..97b230fe1 100644 --- a/lib/teams/server/message-actions/clan-leave.js +++ b/lib/teams/server/message-actions/clan-leave.js @@ -33,8 +33,8 @@ class ClanLeave Logger.error('Player "'+playerSchema.player_id+'" current clan "'+clanId+'" not found.'); return false; } - let playerIds = Object.keys(currentClan.players); - let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; + let disbandClan = playerSchema.id === currentClan.owner.id; + let removeByKeys = disbandClan ? Object.keys(currentClan.players) : [singleRemoveId]; for(let playerId of removeByKeys){ let sendUpdate = { act: TeamsConst.ACTIONS.CLAN_INITIALIZE, diff --git a/lib/teams/server/message-actions/try-clan-invite.js b/lib/teams/server/message-actions/try-clan-invite.js index eb6e523ac..e9feb7b2f 100644 --- a/lib/teams/server/message-actions/try-clan-invite.js +++ b/lib/teams/server/message-actions/try-clan-invite.js @@ -12,13 +12,24 @@ class TryClanInvite static async execute(client, data, room, playerSchema, teamsPlugin) { + // @TODO - BETA - Replace send data by short constants so we will know like "data.id" here is the target ID. if(playerSchema.sessionId === data.id){ Logger.info('The player is trying to clan up with himself.', playerSchema.sessionId, data); return false; } - let playerClan = playerSchema.getPrivate('clan'); + let playerClanId = playerSchema.getPrivate('clan'); + if(!playerClanId){ + Logger.error('Player without a clan is trying to send a clan invite.', playerSchema.sessionId, data); + return false; + } + let playerClan = teamsPlugin.clans[playerClanId]; if(!playerClan){ - Logger.warning('Player without a clan is trying to send a clan invite.', playerSchema.sessionId, data); + Logger.critical( + 'Player has a clan ID but clan entity does not exists on TeamsPlugin.', + playerSchema.sessionId, + data, + {availableClans: Object.keys(teamsPlugin.clans)} + ); return false; } let toPlayer = sc.get(room.activePlayers, data.id, false); @@ -39,6 +50,7 @@ class TryClanInvite return false; } toPlayer.client.send('*', sendData); + playerClan.pendingInvites[data.id] = true; return true; } } diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index 51dd8a584..c8573f51c 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -5,6 +5,7 @@ */ const { ErrorManager, sc } = require('@reldens/utils'); +const {ModifierConst} = require("@reldens/modifiers"); class Team { @@ -34,7 +35,7 @@ class Team { this.players[playerSchema.id] = playerSchema; this.clients[playerSchema.id] = client; - this.applyModifiers(playerSchema); + return this.applyModifiers(playerSchema); } leave(playerSchema) @@ -43,18 +44,23 @@ class Team playerSchema.currentTeam = false; delete this.clients[playerSchema.id]; delete this.players[playerSchema.id]; + return true; } applyModifiers(playerSchema) { let modifiersKeys = Object.keys(this.modifiers); if(0 === modifiersKeys.length){ - return false; + return true; } for(let i of modifiersKeys){ let modifier = this.modifiers[i]; modifier.apply(playerSchema); + if(ModifierConst.MOD_APPLIED !== modifier.state){ + return false; + } } + return true; } revertModifiers(playerSchema) @@ -66,7 +72,11 @@ class Team for(let i of modifiersKeys){ let modifier = this.modifiers[i]; modifier.revert(playerSchema); + if(ModifierConst.MOD_REVERTED !== modifier.state){ + return false; + } } + return true; } } From 71a200e876bc4248fb9b15dda969c50bc0e9035c Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 12 Apr 2023 20:11:08 +0200 Subject: [PATCH 41/82] - Reldens - v4.0.0 - Clan leave fix. --- lib/teams/server/clan.js | 9 +++ package-lock.json | 136 ++++++++++++++------------------------- package.json | 4 +- 3 files changed, 61 insertions(+), 88 deletions(-) diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index 0a43c8f06..8d04014bb 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -91,6 +91,15 @@ class Clan extends Team return result; } + leave(playerSchema) + { + this.revertModifiers(playerSchema); + playerSchema.currentClan = false; + delete this.clients[playerSchema.id]; + delete this.players[playerSchema.id]; + return true; + } + } module.exports.Clan = Clan; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ee061452b..c57557920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,9 +54,9 @@ "@parcel/transformer-webmanifest": "2.8.0", "@parcel/transformer-worklet": "2.8.0", "@reldens/items-system": "^0.18.1", - "@reldens/modifiers": "^0.19.0", + "@reldens/modifiers": "^0.19.1", "@reldens/skills": "^0.18.0", - "@reldens/storage": "^0.14.0", + "@reldens/storage": "^0.15.0", "@reldens/utils": "^0.20.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", @@ -4068,16 +4068,16 @@ } }, "node_modules/@mikro-orm/core": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.15.tgz", - "integrity": "sha512-LcyHVEW6RO6o1ZA1mVdNypsdx5uGkV19LkKNv4GS88cJxTXzYuuvthv3qZNbrTfDqJojgf+dcI3wQn/9Pbi0Cw==", + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.16.tgz", + "integrity": "sha512-JTrVS4Rb5uVKbf/d26Ni/YgJjMyoHapPEUklr4H+4c+6xlRXbfpPDN6o4ESWNXTc4ubwRGLMI1haMXg0bh6uVQ==", "dependencies": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", - "fs-extra": "11.1.0", + "fs-extra": "11.1.1", "globby": "11.1.0", - "mikro-orm": "~5.6.15", + "mikro-orm": "~5.6.16", "reflect-metadata": "0.1.13" }, "engines": { @@ -4129,9 +4129,9 @@ } }, "node_modules/@mikro-orm/mongodb": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.15.tgz", - "integrity": "sha512-nBHVWLP67ZOVyTlQV+Mvf0RIyi75vfJkU6jsKRSTr8vhnQTizkMxYwyTYUyt1GzlJwajqgwpxQEf57w9SXpE3Q==", + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.16.tgz", + "integrity": "sha512-MH7B4+OkQedLkv+ecTDRD41La8255LfpCuuwHNOnE8+XjF0tCXiD1XO4Cpcau98ubLR959HRs88OtulQwzgCQw==", "dependencies": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -5477,19 +5477,11 @@ } }, "node_modules/@reldens/modifiers": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.0.tgz", - "integrity": "sha512-tJyHi7NIOpANh+8lBmq7GpD5xu6L2tGutvfd8+RaliFy5BMXzwBM7ce9+tKd3Ja5diUi5WMA8w9xKT5lZ+327w==", - "dependencies": { - "@reldens/utils": "^0.19.0" - } - }, - "node_modules/@reldens/modifiers/node_modules/@reldens/utils": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", - "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.1.tgz", + "integrity": "sha512-WBN2WCxszwa00tVgGWqwcYeEuF7NrN1FkETWT2Q6lFSwbkIpVMxuwcz9gXsSJHG0fvPZHBfIlgYsHxc45hQO6Q==", "dependencies": { - "await-event-emitter": "^2.0.2" + "@reldens/utils": "^0.20.0" } }, "node_modules/@reldens/skills": { @@ -5532,26 +5524,18 @@ } }, "node_modules/@reldens/storage": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.14.0.tgz", - "integrity": "sha512-z87VGmJiHO6kKCYIMxeZWcIgZzisw1DINfDq9unlh5ewO4rIgabOdcl1LKBOQpf0TJ3EesVD77C1eJu8B6weHg==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.15.0.tgz", + "integrity": "sha512-xCzFREU0IxmSdnSsYjOp8ym2X5G/MfTFw0NWtfoYWLHJ17RbN9x3KpnCBMkllJzkb19tGUO3pqX1L9bocgSDgg==", "dependencies": { - "@mikro-orm/core": "^5.6.15", - "@mikro-orm/mongodb": "^5.6.15", - "@reldens/utils": "^0.19.0", + "@mikro-orm/core": "^5.6.16", + "@mikro-orm/mongodb": "^5.6.16", + "@reldens/utils": "^0.20.0", "knex": "^2.4.2", "mysql": "^2.18.1", "objection": "^3.0.1" } }, - "node_modules/@reldens/storage/node_modules/@reldens/utils": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", - "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", - "dependencies": { - "await-event-emitter": "^2.0.2" - } - }, "node_modules/@reldens/utils": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.20.0.tgz", @@ -7963,9 +7947,9 @@ } }, "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -9482,9 +9466,9 @@ } }, "node_modules/mikro-orm": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.15.tgz", - "integrity": "sha512-DlErpPXJ1gOqbDWgRXq5yEuhuOZP5QXC9EoP8n0OL8klB/Nm/qNRN0ATe0+9TyYdQHNe8BdpPMTwDee56VzdVA==", + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.16.tgz", + "integrity": "sha512-HgG079qA5hWgGWlq9u3BjgE3ynGnDFsGRtvFhgo6W3Itkz46SsQ4oeQxRcAetd8mj/qM4SOLuy0k71pI6h0PkQ==", "engines": { "node": ">= 14.0.0" } @@ -15612,23 +15596,23 @@ } }, "@mikro-orm/core": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.15.tgz", - "integrity": "sha512-LcyHVEW6RO6o1ZA1mVdNypsdx5uGkV19LkKNv4GS88cJxTXzYuuvthv3qZNbrTfDqJojgf+dcI3wQn/9Pbi0Cw==", + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-5.6.16.tgz", + "integrity": "sha512-JTrVS4Rb5uVKbf/d26Ni/YgJjMyoHapPEUklr4H+4c+6xlRXbfpPDN6o4ESWNXTc4ubwRGLMI1haMXg0bh6uVQ==", "requires": { "acorn-loose": "8.3.0", "acorn-walk": "8.2.0", "dotenv": "16.0.3", - "fs-extra": "11.1.0", + "fs-extra": "11.1.1", "globby": "11.1.0", - "mikro-orm": "~5.6.15", + "mikro-orm": "~5.6.16", "reflect-metadata": "0.1.13" } }, "@mikro-orm/mongodb": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.15.tgz", - "integrity": "sha512-nBHVWLP67ZOVyTlQV+Mvf0RIyi75vfJkU6jsKRSTr8vhnQTizkMxYwyTYUyt1GzlJwajqgwpxQEf57w9SXpE3Q==", + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/@mikro-orm/mongodb/-/mongodb-5.6.16.tgz", + "integrity": "sha512-MH7B4+OkQedLkv+ecTDRD41La8255LfpCuuwHNOnE8+XjF0tCXiD1XO4Cpcau98ubLR959HRs88OtulQwzgCQw==", "requires": { "bson": "^4.7.0", "mongodb": "4.13.0" @@ -16470,21 +16454,11 @@ } }, "@reldens/modifiers": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.0.tgz", - "integrity": "sha512-tJyHi7NIOpANh+8lBmq7GpD5xu6L2tGutvfd8+RaliFy5BMXzwBM7ce9+tKd3Ja5diUi5WMA8w9xKT5lZ+327w==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@reldens/modifiers/-/modifiers-0.19.1.tgz", + "integrity": "sha512-WBN2WCxszwa00tVgGWqwcYeEuF7NrN1FkETWT2Q6lFSwbkIpVMxuwcz9gXsSJHG0fvPZHBfIlgYsHxc45hQO6Q==", "requires": { - "@reldens/utils": "^0.19.0" - }, - "dependencies": { - "@reldens/utils": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", - "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", - "requires": { - "await-event-emitter": "^2.0.2" - } - } + "@reldens/utils": "^0.20.0" } }, "@reldens/skills": { @@ -16531,26 +16505,16 @@ } }, "@reldens/storage": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.14.0.tgz", - "integrity": "sha512-z87VGmJiHO6kKCYIMxeZWcIgZzisw1DINfDq9unlh5ewO4rIgabOdcl1LKBOQpf0TJ3EesVD77C1eJu8B6weHg==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@reldens/storage/-/storage-0.15.0.tgz", + "integrity": "sha512-xCzFREU0IxmSdnSsYjOp8ym2X5G/MfTFw0NWtfoYWLHJ17RbN9x3KpnCBMkllJzkb19tGUO3pqX1L9bocgSDgg==", "requires": { - "@mikro-orm/core": "^5.6.15", - "@mikro-orm/mongodb": "^5.6.15", - "@reldens/utils": "^0.19.0", + "@mikro-orm/core": "^5.6.16", + "@mikro-orm/mongodb": "^5.6.16", + "@reldens/utils": "^0.20.0", "knex": "^2.4.2", "mysql": "^2.18.1", "objection": "^3.0.1" - }, - "dependencies": { - "@reldens/utils": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@reldens/utils/-/utils-0.19.0.tgz", - "integrity": "sha512-GhRQaOotANHyAmDu6so1OJQwSyzmtFymzQXPQt8yFweRqCJudOtNxq2pYd9C5fN7NGC5W6w+lXUbhjSN2erpUA==", - "requires": { - "await-event-emitter": "^2.0.2" - } - } } }, "@reldens/utils": { @@ -18455,9 +18419,9 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -19495,9 +19459,9 @@ } }, "mikro-orm": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.15.tgz", - "integrity": "sha512-DlErpPXJ1gOqbDWgRXq5yEuhuOZP5QXC9EoP8n0OL8klB/Nm/qNRN0ATe0+9TyYdQHNe8BdpPMTwDee56VzdVA==" + "version": "5.6.16", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-5.6.16.tgz", + "integrity": "sha512-HgG079qA5hWgGWlq9u3BjgE3ynGnDFsGRtvFhgo6W3Itkz46SsQ4oeQxRcAetd8mj/qM4SOLuy0k71pI6h0PkQ==" }, "mime": { "version": "3.0.0", diff --git a/package.json b/package.json index f10dd9c1c..92befafe9 100644 --- a/package.json +++ b/package.json @@ -79,9 +79,9 @@ "@parcel/reporter-dev-server": "2.8.0", "@parcel/core": "2.8.0", "@reldens/items-system": "^0.18.1", - "@reldens/modifiers": "^0.19.0", + "@reldens/modifiers": "^0.19.1", "@reldens/skills": "^0.18.0", - "@reldens/storage": "^0.14.0", + "@reldens/storage": "^0.15.0", "@reldens/utils": "^0.20.0", "adminjs": "5.7.3", "bcrypt": "^5.1.0", From 4832fd34ef8446a15727eef2edcf767c73107994 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Fri, 14 Apr 2023 20:54:57 +0200 Subject: [PATCH 42/82] - Reldens - v4.0.0 - Clan members, target display and disconnect. --- lib/rooms/server/scene.js | 2 +- lib/teams/client/clan-message-handler.js | 44 ++++++++++++++++--- lib/teams/client/clan-message-listener.js | 1 + lib/teams/client/target-box-enricher.js | 4 +- lib/teams/client/templates-handler.js | 1 + lib/teams/constants.js | 4 +- lib/teams/server/clan.js | 24 ++++++---- .../create-player-clan-handler.js | 7 +-- .../server/message-actions/clan-create.js | 3 +- .../server/message-actions/clan-disconnect.js | 34 +++++++------- lib/teams/server/message-actions/clan-join.js | 11 ++++- .../server/message-actions/clan-leave.js | 2 + lib/teams/server/plugin.js | 8 ++-- lib/teams/server/team.js | 2 +- lib/users/client/player-engine.js | 7 ++- migrations/production/beta.26-sql-update.sql | 2 +- .../teams/templates/clan-container.html | 13 +++++- .../teams/templates/clan-member-data.html | 6 +++ .../teams/templates/clan-player-data.html | 1 - .../features/teams/templates/clan-remove.html | 2 +- theme/default/css/teams.scss | 8 ++++ 21 files changed, 129 insertions(+), 57 deletions(-) create mode 100644 theme/default/assets/features/teams/templates/clan-member-data.html diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index f643ad896..0309a79b6 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -389,7 +389,7 @@ class RoomScene extends RoomLogin let savedPlayer = await this.savePlayerState(sessionId); let playerSchema = this.getPlayerFromState(sessionId); let stateObject = {isRemoveReady: true}; - await this.events.emit('reldens.removeAllPlayerReferencesBefore', { + await this.events.emit('reldens.saveStateAndRemovePlayerBefore', { room: this, savedPlayer, playerSchema, diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 0ad1b8bda..32044b716 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -131,12 +131,14 @@ class ClanMessageHandler setClanFromMessage() { let players = sc.get(this.message, 'players', false); + let members = sc.get(this.message, 'members', false); this.uiScene.currentClan = { id: this.message.id, name: this.message.clanName, leader: this.message.leaderName, ownerId: this.message.ownerId, - players + players, + members }; } @@ -223,11 +225,16 @@ class ClanMessageHandler updateClanBox(container) { let players = sc.get(this.message, 'players', []); - let clanMembers = ''; + let clanPlayers = ''; for(let i of Object.keys(players)){ - clanMembers += this.createClanMemberBox(players[i]); + clanPlayers += this.createClanPlayerBox(players[i]); + } + let members = sc.get(this.message, 'members', []); + let clanMembers = ''; + for(let i of Object.keys(members)){ + clanMembers += this.createClanMemberBox(members[i]); } - container.innerHTML = this.createClanContainer(clanMembers); + container.innerHTML = this.createClanContainer(clanPlayers, clanMembers); this.activateClanTargetActions(players); this.activateClanLeaveButtonAction(); } @@ -240,7 +247,7 @@ class ClanMessageHandler dynamicScene.addAndRemoveCapture(keys, inputElement); } - createClanContainer(clanMembers) + createClanContainer(clanPlayers, clanMembers) { // @TODO - BETA - Move the template load from cache as part of the engine driver. let templateContent = this.uiScene.cache.html.get('clanContainer'); @@ -256,6 +263,15 @@ class ClanMessageHandler clanId: this.message.id, playerId: this.gameManager.getCurrentPlayer().playerId, leaveActionLabel: leaveActionLabel, + clanPlayersTitle: this.gameManager.config.getWithoutLogs( + 'client/clan/labels/clanPlayersTitle', + TeamsConst.LABELS.CLAN.PLAYERS_TITLE + ), + clanPlayers, + clanMembersTitle: this.gameManager.config.getWithoutLogs( + 'client/clan/labels/clanMembersTitle', + TeamsConst.LABELS.CLAN.MEMBERS_TITLE + ), clanMembers }; return this.gameManager.gameEngine.parseTemplate(templateContent, templateParams); @@ -273,7 +289,7 @@ class ClanMessageHandler }); } - createClanMemberBox(playerData) + createClanPlayerBox(playerData) { // @TODO - BETA - Move the template load from cache as part of the engine driver. let templateContent = this.uiScene.cache.html.get('clanPlayerData'); @@ -281,11 +297,25 @@ class ClanMessageHandler Logger.error('Missing template "clanPlayerData".'); return ''; } - let isPlayerOwner = playerData.id === this.message.ownerId; return this.gameManager.gameEngine.parseTemplate(templateContent, { playerId: playerData.id, playerName: playerData.name, playerProperties: this.createSharedPropertiesContent(playerData.sharedProperties), + }); + } + + createClanMemberBox(playerData) + { + // @TODO - BETA - Move the template load from cache as part of the engine driver. + let templateContent = this.uiScene.cache.html.get('clanMemberData'); + if(!templateContent){ + Logger.error('Missing template "clanMemberData".'); + return ''; + } + let isPlayerOwner = playerData.id.toString() === this.message.ownerId.toString(); + return this.gameManager.gameEngine.parseTemplate(templateContent, { + playerId: playerData.id, + playerName: playerData.name, playerRemove: isPlayerOwner ? this.createDismissPlayerButton(playerData) : '' }); } diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js index 306af7a37..7b62f2716 100644 --- a/lib/teams/client/clan-message-listener.js +++ b/lib/teams/client/clan-message-listener.js @@ -63,6 +63,7 @@ class ClanMessageListener { return 0 === message.act.indexOf(TeamsConst.CLAN_PREF); } + } module.exports.ClanMessageListener = ClanMessageListener; diff --git a/lib/teams/client/target-box-enricher.js b/lib/teams/client/target-box-enricher.js index 1ea85a580..5d81cae78 100644 --- a/lib/teams/client/target-box-enricher.js +++ b/lib/teams/client/target-box-enricher.js @@ -18,7 +18,7 @@ class TargetBoxEnricher // current player has none clan: return false; } - if(currentClan.players[target.id]){ + if(currentClan.members[target.id]){ // target player is already on the clan: return false; } @@ -28,7 +28,7 @@ class TargetBoxEnricher return false; } let isOpenInvites = gameManager.config.getWithoutLogs('client/clan/general/openInvites', false); - if(gameManager.playerData.id !== currentClan.ownerId && !isOpenInvites){ + if(gameManager.playerData.id.toString() !== currentClan.ownerId.toString() && !isOpenInvites){ // only clan owner can invite: return false; } diff --git a/lib/teams/client/templates-handler.js b/lib/teams/client/templates-handler.js index 8bf88d1e7..1fe432939 100644 --- a/lib/teams/client/templates-handler.js +++ b/lib/teams/client/templates-handler.js @@ -25,6 +25,7 @@ class TemplatesHandler preloadScene.load.html('clanRemove', teamsTemplatePath+'clan-remove.html'); preloadScene.load.html('clanContainer', teamsTemplatePath+'clan-container.html'); preloadScene.load.html('clanPlayerData', teamsTemplatePath+'clan-player-data.html'); + preloadScene.load.html('clanMemberData', teamsTemplatePath+'clan-member-data.html'); preloadScene.load.html('teamsSharedProperty', teamsTemplatePath+'shared-property.html'); } diff --git a/lib/teams/constants.js b/lib/teams/constants.js index ba4912a7c..aceedcd69 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -58,7 +58,9 @@ module.exports.TeamsConst = { CREATE: 'Create', DISBAND: 'Disband Clan', LEAVE: 'Leave Clan', - PROPERTY_MAX_VALUE: '/ %propertyMaxValue' + PROPERTY_MAX_VALUE: '/ %propertyMaxValue', + PLAYERS_TITLE: 'Connected Players:', + MEMBERS_TITLE: 'Clan Members:', } } }; diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index 8d04014bb..50cab99e7 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -56,11 +56,11 @@ class Clan extends Team level: this.level, ownerId: this.owner.player_id, leaderName: this.owner.playerName, - members: this.mapMembersToArray() + members: this.membersToArray() }; } - mapMembersToArray() + membersToArray() { if(0 === sc.length(this.members)){ return []; @@ -68,30 +68,38 @@ class Clan extends Team let members = {}; for(let i of Object.keys(this.members)){ let member = this.members[i]; + let parentPlayer = member?.parent_player; + if(!parentPlayer){ + Logger.error('Member player not available.', member); + continue; + } members[i] = { // @TODO - BETA - Make other members data optional, like level or class path. - name: member?.parent_player?.name, + name: parentPlayer.name, id: i } } return members; } - join(playerSchema, client) + join(playerSchema, client, clanMemberModel) { let result = super.join(playerSchema, client); if(!result){ Logger.error('Player could not join the clan.', playerSchema.id, result, this.players); return false; } - this.members[playerSchema.id] = { - id: playerSchema.id, - name: playerSchema.playerName - } + this.members[playerSchema.id] = clanMemberModel; return result; } leave(playerSchema) + { + delete this.members[playerSchema.id]; + return this.disconnect(playerSchema); + } + + disconnect(playerSchema) { this.revertModifiers(playerSchema); playerSchema.currentClan = false; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 86c4a45fe..b7e7b4464 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -19,9 +19,10 @@ class CreatePlayerClanHandler if(!startEvent.continueProcess){ return false; } - let clanMemberModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneBy( + let clanMemberModel = await teamsPlugin.dataServer.getEntity('clanMembers').loadOneByWithRelations( 'player_id', - playerSchema.player_id + playerSchema.player_id, + ['parent_player'] ); if(!clanMemberModel){ let sendData = { @@ -36,7 +37,7 @@ class CreatePlayerClanHandler Logger.error('Loading clan by ID "'+clanMemberModel.clan_id+'" not found.', clan); return false; } - clan.join(playerSchema, client); + clan.join(playerSchema, client, clanMemberModel); playerSchema.setPrivate('clan', clanMemberModel.clan_id); let endEvent = {client, playerSchema, room, teamsPlugin, continueProcess: true}; teamsPlugin.events.emit('reldens.beforeEnrichPlayerWithClanUpdate', endEvent); diff --git a/lib/teams/server/message-actions/clan-create.js b/lib/teams/server/message-actions/clan-create.js index e7d44aa79..9cfe7b87b 100644 --- a/lib/teams/server/message-actions/clan-create.js +++ b/lib/teams/server/message-actions/clan-create.js @@ -4,9 +4,9 @@ * */ +const { ClanFactory } = require('../clan-factory'); const { TeamsConst } = require('../../constants'); const { Logger, sc } = require('@reldens/utils'); -const {ClanFactory} = require("../clan-factory"); class ClanCreate { @@ -58,6 +58,7 @@ class ClanCreate room.config.get('client/ui/teams/sharedProperties'), teamsPlugin ); + Logger.info('New clan created "'+clanName+'".', ownerMember); return this.clanCreateSend( client, TeamsConst.VALIDATION.SUCCESS, diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 95b8ae1b8..5eaf0a4d5 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -13,49 +13,45 @@ class ClanDisconnect static async execute(playerSchema, teamsPlugin) { + // @TODO - BETA - Consider extend TeamLeave. + if(!playerSchema){ + Logger.info('Player already left, wont disconnect from clan.'); + return false; + } let clanId = playerSchema?.privateData?.clan; if(!clanId){ - Logger.warning('Clan ID not found in current player for disconnection.', playerSchema); + Logger.warning('Clan ID not found in current player for disconnection.', playerSchema.player_id); return false; } - let currentClan = teamsPlugin.clans[clanId]; - if(!currentClan){ + let clan = teamsPlugin.clans[clanId]; + if(!clan){ Logger.error('Player "'+playerSchema.player_id+'" current clan "'+clanId+'" not found for disconnection.'); return false; } // @NOTE: the way this works is by making the clients leave the clan and then updating the remaining players. - let playerIds = Object.keys(currentClan.players); + let playerIds = Object.keys(clan.players); let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [playerSchema.player_id]; for(let playerId of removeByKeys){ let sendUpdate = { act: TeamsConst.ACTIONS.CLAN_LEFT, - id: currentClan.ownerClient.id, + id: clan.ownerClient.id, listener: TeamsConst.KEY }; - await teamsPlugin.events.emit('reldens.clanLeaveBeforeSendUpdate', { + await teamsPlugin.events.emit('reldens.clanDisconnectBeforeSendUpdate', { playerId, sendUpdate, playerSchema, teamsPlugin }); - currentClan.clients[playerId].send('*', sendUpdate); - currentClan.leave(currentClan.players[playerId]); - } - if(0 === Object.keys(currentClan.members).length){ - let event = {playerSchema, teamsPlugin, continueDisband: true}; - await teamsPlugin.events.emit('reldens.beforeClanTeamDisband', event); - if(!event.continueDisband){ - return false; - } - delete teamsPlugin.clans[clanId]; - return true; + clan.clients[playerId].send('*', sendUpdate); + clan.disconnect(clan.players[playerId]); } let event = {playerSchema, teamsPlugin, continueLeave: true}; - await teamsPlugin.events.emit('reldens.clanLeaveAfterSendUpdate', event); + await teamsPlugin.events.emit('reldens.clanDisconnectAfterSendUpdate', event); if(!event.continueLeave){ return false; } - return ClanUpdatesHandler.updateClanPlayers(currentClan); + return ClanUpdatesHandler.updateClanPlayers(clan); } } diff --git a/lib/teams/server/message-actions/clan-join.js b/lib/teams/server/message-actions/clan-join.js index ac9119802..ec769ed25 100644 --- a/lib/teams/server/message-actions/clan-join.js +++ b/lib/teams/server/message-actions/clan-join.js @@ -4,7 +4,6 @@ * */ -const { Clan } = require('../clan'); const { ClanUpdatesHandler } = require('../clan-updates-handler'); const { Logger } = require('@reldens/utils'); @@ -35,7 +34,15 @@ class ClanJoin Logger.critical('Clan member could not be created.', playerSchema?.id, clanToJoin?.id); return false; } - clanToJoin.join(playerSchema, client); + clanToJoin.join( + playerSchema, + client, + // @TODO - BETA - Replace this query with a constructed member, use the exiting player model and the result. + await teamsPlugin.dataServer.getEntity('clanMembers').loadOneByWithRelations( + {player_id: playerSchema.id, clan_id: clanToJoin.id}, + ['parent_player'] + ) + ); playerSchema.setPrivate('clan', clanToJoin.id); let eventBeforeJoinUpdate = {clanToJoin, teamsPlugin, continueBeforeJoinUpdate: true}; await teamsPlugin.events.emit('reldens.beforeClanUpdatePlayers', eventBeforeJoinUpdate); diff --git a/lib/teams/server/message-actions/clan-leave.js b/lib/teams/server/message-actions/clan-leave.js index 97b230fe1..e033c27a2 100644 --- a/lib/teams/server/message-actions/clan-leave.js +++ b/lib/teams/server/message-actions/clan-leave.js @@ -51,6 +51,8 @@ class ClanLeave currentClan.clients[playerId].send('*', sendUpdate); currentClan.leave(currentClan.players[playerId]); delete currentClan.members[playerId]; + delete currentClan.players[playerId]; + delete currentClan.clients[playerId]; await teamsPlugin.dataServer.getEntity('clanMembers').deleteBy({'player_id': playerId}); } if(0 === Object.keys(currentClan.members).length){ diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 96d15b9ba..bbe63a342 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -11,8 +11,9 @@ const { CreatePlayerClanHandler } = require('./event-handlers/create-player-clan const { CreatePlayerTeamHandler } = require('./event-handlers/create-player-team-handler'); const { StatsUpdateHandler } = require('./event-handlers/stats-update-handler'); const { EndPlayerHitChangePointTeamHandler } = require('./event-handlers/end-player-hit-change-point-team-handler'); +const { TeamLeave } = require('./message-actions/team-leave'); +const { ClanDisconnect } = require('./message-actions/clan-disconnect'); const { Logger, sc } = require('@reldens/utils'); -const {TeamLeave} = require("./message-actions/team-leave"); class TeamsPlugin extends PluginInterface { @@ -45,8 +46,9 @@ class TeamsPlugin extends PluginInterface this.events.on('reldens.endPlayerHitChangePoint', async (event) => { await EndPlayerHitChangePointTeamHandler.savePlayerTeam(event.playerSchema, this); }); - this.events.on('reldens.removeAllPlayerReferencesBefore', async(event) => { - return await TeamLeave.execute(event.room, event.playerSchema, this); + this.events.on('reldens.saveStateAndRemovePlayerBefore', async(event) => { + await TeamLeave.execute(event.room, event.playerSchema, this); + await ClanDisconnect.execute(event.room, event.playerSchema, this); }); } diff --git a/lib/teams/server/team.js b/lib/teams/server/team.js index c8573f51c..fe7c79b36 100644 --- a/lib/teams/server/team.js +++ b/lib/teams/server/team.js @@ -4,8 +4,8 @@ * */ +const { ModifierConst } = require('@reldens/modifiers'); const { ErrorManager, sc } = require('@reldens/utils'); -const {ModifierConst} = require("@reldens/modifiers"); class Team { diff --git a/lib/users/client/player-engine.js b/lib/users/client/player-engine.js index aea92719e..a8335970c 100644 --- a/lib/users/client/player-engine.js +++ b/lib/users/client/player-engine.js @@ -2,14 +2,12 @@ * * Reldens - PlayerEngine * - * PlayerEngine is the class that handle the player actions in the client side. - * */ const { SpriteTextFactory } = require('../../game/client/engine/sprite-text-factory'); -const { Logger, sc } = require('@reldens/utils'); const { GameConst } = require('../../game/constants'); const { ActionsConst } = require('../../actions/constants'); +const { Logger, sc } = require('@reldens/utils'); class PlayerEngine { @@ -103,7 +101,8 @@ class PlayerEngine setTargetPlayerById(id) { - if(sc.get(this.players, 'id', false)){ + if(!sc.get(this.players, id, false)){ + Logger.info('Target player ID "'+id+'" was not found.'); this.gameManager.gameEngine.clearTarget(); return false; } diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index a1169d200..9837daa5c 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -47,7 +47,7 @@ INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/responsiveY', '0', @float INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/x', '430', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/y', '100', @float_id); INSERT INTO `config` VALUES (NULL, 'client', 'ui/teams/sharedProperties', '{"hp":{"path":"stats/hp","pathMax":"statsBase/hp","label":"HP"},"mp":{"path":"stats/mp","pathMax":"statsBase/mp","label":"MP"}}', @json_id); -INSERT INTO `config` VALUES (NULL, 'client', 'clan/general/openInvites', '1', @boolean_id); +INSERT INTO `config` VALUES (NULL, 'client', 'clan/general/openInvites', '0', @boolean_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/requestFromTitle', 'Clan request from:', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/clanTitle', 'Clan: %clanName - Leader: %leaderName', @string_id); INSERT INTO `config` VALUES (NULL, 'client', 'clan/labels/propertyMaxValue', '/ %propertyMaxValue', @string_id); diff --git a/theme/default/assets/features/teams/templates/clan-container.html b/theme/default/assets/features/teams/templates/clan-container.html index 40a2c4dfe..3213bb43e 100644 --- a/theme/default/assets/features/teams/templates/clan-container.html +++ b/theme/default/assets/features/teams/templates/clan-container.html @@ -1,6 +1,15 @@
-
- {{&clanMembers}} +
+
{{clanPlayersTitle}}
+
+ {{&clanPlayers}} +
+
+
+
{{clanMembersTitle}}
+
+ {{&clanMembers}} +
diff --git a/theme/default/assets/features/teams/templates/clan-member-data.html b/theme/default/assets/features/teams/templates/clan-member-data.html new file mode 100644 index 000000000..1c15f8d04 --- /dev/null +++ b/theme/default/assets/features/teams/templates/clan-member-data.html @@ -0,0 +1,6 @@ +
+
+ {{playerName}} +
+ {{&clanRemove}} +
diff --git a/theme/default/assets/features/teams/templates/clan-player-data.html b/theme/default/assets/features/teams/templates/clan-player-data.html index 76b31418e..79733ff44 100644 --- a/theme/default/assets/features/teams/templates/clan-player-data.html +++ b/theme/default/assets/features/teams/templates/clan-player-data.html @@ -2,7 +2,6 @@
{{playerName}}
- {{&clanRemove}}
{{&playerProperties}}
diff --git a/theme/default/assets/features/teams/templates/clan-remove.html b/theme/default/assets/features/teams/templates/clan-remove.html index 62a0b53c9..a95c48074 100644 --- a/theme/default/assets/features/teams/templates/clan-remove.html +++ b/theme/default/assets/features/teams/templates/clan-remove.html @@ -1,3 +1,3 @@
- remove + remove
diff --git a/theme/default/css/teams.scss b/theme/default/css/teams.scss index fc9ee31fc..c49d5db83 100644 --- a/theme/default/css/teams.scss +++ b/theme/default/css/teams.scss @@ -23,6 +23,13 @@ transform: scale(1.009); */ + .clan-row { + display: block; + float: left; + width: 100%; + margin-top: 10px; + } + .clan-name-input { outline: none; } @@ -30,6 +37,7 @@ .default-loading-container img{ max-width: 48px; } + } @media (max-height: 400px) { From 985c136f773c82d1af62084829302f2332d12784 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Sun, 16 Apr 2023 21:12:45 +0200 Subject: [PATCH 43/82] - Reldens - v4.0.0 - Clan disconnect and target fixes. --- lib/teams/client/clan-message-handler.js | 8 ++--- lib/teams/client/target-box-enricher.js | 17 ++++++++++- lib/teams/client/team-message-handler.js | 8 ++--- lib/teams/server/clan-updates-handler.js | 3 ++ lib/teams/server/clan.js | 14 +++++++++ .../server/message-actions/clan-disconnect.js | 30 +++++++++++-------- .../server/message-actions/team-leave.js | 30 ++++++++++--------- .../server/message-actions/try-clan-invite.js | 16 ++++++---- lib/teams/server/plugin.js | 2 +- 9 files changed, 85 insertions(+), 43 deletions(-) diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 32044b716..93c6a2c1a 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -23,19 +23,19 @@ class ClanMessageHandler validate() { if(!this.roomEvents){ - Logger.notice('Missing RoomEvents on TeamMessageHandler.'); + Logger.info('Missing RoomEvents on ClanMessageHandler.'); return false; } if(!this.message){ - Logger.notice('Missing message on TeamMessageHandler.'); + Logger.info('Missing message on ClanMessageHandler.'); return false; } if(!this.gameManager){ - Logger.notice('Missing GameManager on TeamMessageHandler.'); + Logger.info('Missing GameManager on ClanMessageHandler.'); return false; } if(!this.uiScene){ - Logger.notice('Missing UI Scene on TeamMessageHandler.'); + Logger.info('Missing UI Scene on ClanMessageHandler.'); return false; } return this.gameManager.getCurrentPlayer().playerId; diff --git a/lib/teams/client/target-box-enricher.js b/lib/teams/client/target-box-enricher.js index 5d81cae78..eab7d2faf 100644 --- a/lib/teams/client/target-box-enricher.js +++ b/lib/teams/client/target-box-enricher.js @@ -18,7 +18,7 @@ class TargetBoxEnricher // current player has none clan: return false; } - if(currentClan.members[target.id]){ + if(this.playerBySessionId(currentClan, target.id)){ // target player is already on the clan: return false; } @@ -87,6 +87,21 @@ class TargetBoxEnricher { return GameConst.TYPE_PLAYER === target.type && currentPlayer.playerId !== target.id; } + + static playerBySessionId(currentClan, targetId) + { + let playersKeys = Object.keys(currentClan.players); + if(0 === playersKeys.length){ + return false; + } + for(let i of playersKeys){ + if(currentClan.players[i].sessionId === targetId){ + return currentClan.players[i]; + } + } + return false; + } + } module.exports.TargetBoxEnricher = TargetBoxEnricher; diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 222da7b6f..4fa521b3f 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -23,19 +23,19 @@ class TeamMessageHandler validate() { if(!this.roomEvents){ - Logger.notice('Missing RoomEvents on TeamMessageHandler.'); + Logger.info('Missing RoomEvents on TeamMessageHandler.'); return false; } if(!this.message){ - Logger.notice('Missing message on TeamMessageHandler.'); + Logger.info('Missing message on TeamMessageHandler.'); return false; } if(!this.gameManager){ - Logger.notice('Missing GameManager on TeamMessageHandler.'); + Logger.info('Missing GameManager on TeamMessageHandler.'); return false; } if(!this.uiScene){ - Logger.notice('Missing UI Scene on TeamMessageHandler.'); + Logger.info('Missing UI Scene on TeamMessageHandler.'); return false; } return true; diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index c3c18b724..6db3f5976 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -6,6 +6,7 @@ const { PlayersDataMapper } = require('./players-data-mapper'); const { TeamsConst } = require('../constants'); +const {Logger} = require("@reldens/utils"); class ClanUpdatesHandler { @@ -15,10 +16,12 @@ class ClanUpdatesHandler // @TODO - BETA - Consider extend TeamUpdatesHandler. let clientsKeys = Object.keys(clan.clients); if (0 === clientsKeys.length) { + Logger.info('Clan update without clients.', clan); return false; } let playersList = PlayersDataMapper.fetchPlayersData(clan); if(0 === Object.keys(playersList).length){ + Logger.info('Clan update without players.', clan); return false; } for(let i of clientsKeys){ diff --git a/lib/teams/server/clan.js b/lib/teams/server/clan.js index 50cab99e7..48fc6f83b 100644 --- a/lib/teams/server/clan.js +++ b/lib/teams/server/clan.js @@ -108,6 +108,20 @@ class Clan extends Team return true; } + playerBySessionId(sessionId) + { + let playersKeys = Object.keys(this.players); + if(0 === playersKeys.length){ + return false; + } + for(let i of playersKeys){ + if(this.players[i].sessionId === sessionId){ + return this.players[i]; + } + } + return false; + } + } module.exports.Clan = Clan; \ No newline at end of file diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 5eaf0a4d5..080b03a0e 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -20,7 +20,7 @@ class ClanDisconnect } let clanId = playerSchema?.privateData?.clan; if(!clanId){ - Logger.warning('Clan ID not found in current player for disconnection.', playerSchema.player_id); + Logger.warning('Clan ID not found in current player for disconnection.', playerSchema); return false; } let clan = teamsPlugin.clans[clanId]; @@ -32,23 +32,27 @@ class ClanDisconnect let playerIds = Object.keys(clan.players); let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [playerSchema.player_id]; for(let playerId of removeByKeys){ - let sendUpdate = { - act: TeamsConst.ACTIONS.CLAN_LEFT, - id: clan.ownerClient.id, - listener: TeamsConst.KEY - }; - await teamsPlugin.events.emit('reldens.clanDisconnectBeforeSendUpdate', { - playerId, - sendUpdate, - playerSchema, - teamsPlugin - }); - clan.clients[playerId].send('*', sendUpdate); + let client = clan.clients[playerId]; + if(1 === client.ref.readyState){ + let sendUpdate = { + act: TeamsConst.ACTIONS.CLAN_LEFT, + id: clan.ownerClient.id, + listener: TeamsConst.KEY + }; + await teamsPlugin.events.emit('reldens.clanDisconnectBeforeSendUpdate', { + playerId, + sendUpdate, + playerSchema, + teamsPlugin + }); + client.send('*', sendUpdate); + } clan.disconnect(clan.players[playerId]); } let event = {playerSchema, teamsPlugin, continueLeave: true}; await teamsPlugin.events.emit('reldens.clanDisconnectAfterSendUpdate', event); if(!event.continueLeave){ + Logger.info('Stopped event "clanDisconnectAfterSendUpdate".'); return false; } return ClanUpdatesHandler.updateClanPlayers(clan); diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 923cce1e7..6c2d0eb38 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -37,20 +37,22 @@ class TeamLeave let playerIds = Object.keys(currentTeam.players); let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ - let sendUpdate = { - act: TeamsConst.ACTIONS.TEAM_LEFT, - id: currentTeam.ownerClient.id, - listener: TeamsConst.KEY - }; - await teamsPlugin.events.emit('reldens.teamLeaveBeforeSendUpdate', { - playerId, - sendUpdate, - singleRemoveId, - room, - playerSchema, - teamsPlugin - }); - currentTeam.clients[playerId].send('*', sendUpdate); + if(1 === currentTeam.clients[playerId].ref.readyState){ + let sendUpdate = { + act: TeamsConst.ACTIONS.TEAM_LEFT, + id: currentTeam.ownerClient.id, + listener: TeamsConst.KEY + }; + await teamsPlugin.events.emit('reldens.teamLeaveBeforeSendUpdate', { + playerId, + sendUpdate, + singleRemoveId, + room, + playerSchema, + teamsPlugin + }); + currentTeam.clients[playerId].send('*', sendUpdate); + } currentTeam.leave(currentTeam.players[playerId]); } if(1 >= Object.keys(currentTeam.players).length){ diff --git a/lib/teams/server/message-actions/try-clan-invite.js b/lib/teams/server/message-actions/try-clan-invite.js index e9feb7b2f..27c8e45ed 100644 --- a/lib/teams/server/message-actions/try-clan-invite.js +++ b/lib/teams/server/message-actions/try-clan-invite.js @@ -17,13 +17,13 @@ class TryClanInvite Logger.info('The player is trying to clan up with himself.', playerSchema.sessionId, data); return false; } - let playerClanId = playerSchema.getPrivate('clan'); - if(!playerClanId){ + let clanId = playerSchema.getPrivate('clan'); + if(!clanId){ Logger.error('Player without a clan is trying to send a clan invite.', playerSchema.sessionId, data); return false; } - let playerClan = teamsPlugin.clans[playerClanId]; - if(!playerClan){ + let clan = teamsPlugin.clans[clanId]; + if(!clan){ Logger.critical( 'Player has a clan ID but clan entity does not exists on TeamsPlugin.', playerSchema.sessionId, @@ -37,11 +37,15 @@ class TryClanInvite Logger.error('Player not found.', toPlayer, data); return false; } + if(clan.playerBySessionId(data.id)){ + Logger.info('Player already exists in clan.'); + return false; + } let sendData = { act: TeamsConst.ACTIONS.CLAN_INVITE, listener: TeamsConst.CLAN_KEY, from: playerSchema.playerName, - id: playerClan.id, + id: clan.id, ownerId: playerSchema.sessionId }; let event = {client, data, room, playerSchema, teamsPlugin, continueStart: true}; @@ -50,7 +54,7 @@ class TryClanInvite return false; } toPlayer.client.send('*', sendData); - playerClan.pendingInvites[data.id] = true; + clan.pendingInvites[data.id] = true; return true; } } diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index bbe63a342..06643c8f3 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -48,7 +48,7 @@ class TeamsPlugin extends PluginInterface }); this.events.on('reldens.saveStateAndRemovePlayerBefore', async(event) => { await TeamLeave.execute(event.room, event.playerSchema, this); - await ClanDisconnect.execute(event.room, event.playerSchema, this); + await ClanDisconnect.execute(event.playerSchema, this); }); } From e7e9d37f24f877e9efbcc7b97ad81bba9120e30f Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 17 Apr 2023 16:43:56 +0200 Subject: [PATCH 44/82] - Reldens - v4.0.0 - Clan disconnect updates. --- lib/game/client/room-events.js | 4 +- lib/teams/client/clan-message-handler.js | 8 +++- lib/teams/constants.js | 1 + lib/teams/server/clan-updates-handler.js | 16 ++++++-- .../server/message-actions/clan-disconnect.js | 41 ++++++++++--------- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/lib/game/client/room-events.js b/lib/game/client/room-events.js index 0c95e3371..081efdba1 100644 --- a/lib/game/client/room-events.js +++ b/lib/game/client/room-events.js @@ -172,8 +172,10 @@ class RoomEvents { let currentScene = this.getActiveScene(); if(this.playerExists(currentScene, key)){ - // remove your player entity from the game world: currentScene.player.removePlayer(key); + if(currentScene.player.currentTarget?.id === key){ + this.gameEngine.clearTarget(); + } } } diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 93c6a2c1a..12b5654ab 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -225,8 +225,12 @@ class ClanMessageHandler updateClanBox(container) { let players = sc.get(this.message, 'players', []); - let clanPlayers = ''; - for(let i of Object.keys(players)){ + let connectedPlayersKeys = Object.keys(players); + let clanPlayers = 0 === connectedPlayersKeys.length ? this.gameManager.config.getWithoutLogs( + 'client/clan/labels/noneConnected', + TeamsConst.LABELS.CLAN.NONE_CONNECTED + ) : ''; + for(let i of connectedPlayersKeys){ clanPlayers += this.createClanPlayerBox(players[i]); } let members = sc.get(this.message, 'members', []); diff --git a/lib/teams/constants.js b/lib/teams/constants.js index aceedcd69..3d32fb7b7 100644 --- a/lib/teams/constants.js +++ b/lib/teams/constants.js @@ -61,6 +61,7 @@ module.exports.TeamsConst = { PROPERTY_MAX_VALUE: '/ %propertyMaxValue', PLAYERS_TITLE: 'Connected Players:', MEMBERS_TITLE: 'Clan Members:', + NONE_CONNECTED: 'None' } } }; diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 6db3f5976..025846b28 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -6,7 +6,7 @@ const { PlayersDataMapper } = require('./players-data-mapper'); const { TeamsConst } = require('../constants'); -const {Logger} = require("@reldens/utils"); +const { Logger } = require('@reldens/utils'); class ClanUpdatesHandler { @@ -29,8 +29,8 @@ class ClanUpdatesHandler delete otherPlayersData[i]; let sendUpdate = Object.assign( { - act: TeamsConst.ACTIONS.CLAN_UPDATE, - listener: TeamsConst.CLAN_KEY, + act: this.actionConstant(), + listener: this.listenerKey(), players: otherPlayersData, }, clan.toArray() @@ -40,6 +40,16 @@ class ClanUpdatesHandler return true; } + static listenerKey() + { + return TeamsConst.CLAN_KEY; + } + + static actionConstant() + { + return TeamsConst.ACTIONS.CLAN_UPDATE; + } + } module.exports.ClanUpdatesHandler = ClanUpdatesHandler; diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 080b03a0e..580d892b7 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -29,25 +29,28 @@ class ClanDisconnect return false; } // @NOTE: the way this works is by making the clients leave the clan and then updating the remaining players. - let playerIds = Object.keys(clan.players); - let removeByKeys = playerSchema.id === clanId || 2 >= playerIds.length ? playerIds : [playerSchema.player_id]; - for(let playerId of removeByKeys){ - let client = clan.clients[playerId]; - if(1 === client.ref.readyState){ - let sendUpdate = { - act: TeamsConst.ACTIONS.CLAN_LEFT, - id: clan.ownerClient.id, - listener: TeamsConst.KEY - }; - await teamsPlugin.events.emit('reldens.clanDisconnectBeforeSendUpdate', { - playerId, - sendUpdate, - playerSchema, - teamsPlugin - }); - client.send('*', sendUpdate); - } - clan.disconnect(clan.players[playerId]); + let playerId = playerSchema.player_id; + let client = clan.clients[playerId]; + if(1 === client.ref.readyState){ + let sendUpdate = { + act: TeamsConst.ACTIONS.CLAN_LEFT, + id: clan.ownerClient.id, + listener: TeamsConst.KEY + }; + await teamsPlugin.events.emit('reldens.clanDisconnectBeforeSendUpdate', { + playerId, + sendUpdate, + playerSchema, + teamsPlugin + }); + client.send('*', sendUpdate); + } + clan.disconnect(clan.players[playerId]); + let afterDisconnectKeys = Object.keys(clan.players); + if(0 === afterDisconnectKeys.length){ + Logger.info('Last player on clan disconnected.'); + delete teamsPlugin.clans[clanId]; + return true; } let event = {playerSchema, teamsPlugin, continueLeave: true}; await teamsPlugin.events.emit('reldens.clanDisconnectAfterSendUpdate', event); From 0dc87305eac2e3468314777f6af695d800fc38b0 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Mon, 17 Apr 2023 21:02:17 +0200 Subject: [PATCH 45/82] - Reldens - v4.0.0 - Clan room change updates. --- lib/teams/client/clan-message-listener.js | 1 + lib/teams/server/clan-updates-handler.js | 1 + .../create-player-clan-handler.js | 1 + .../create-player-team-handler.js | 2 +- ...nd-player-hit-change-point-team-handler.js | 2 +- .../event-handlers/stats-update-handler.js | 25 +++++++++---------- .../server/message-actions/clan-disconnect.js | 4 +++ lib/teams/server/plugin.js | 3 ++- lib/users/client/player-engine.js | 1 + 9 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js index 7b62f2716..e9c16b344 100644 --- a/lib/teams/client/clan-message-listener.js +++ b/lib/teams/client/clan-message-listener.js @@ -41,6 +41,7 @@ class ClanMessageListener handleClanMessage(message, clanMessageHandler) { + // console.log({message}); if(TeamsConst.ACTIONS.CLAN_INITIALIZE === message.act){ return clanMessageHandler.initializeClanUi(); } diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index 025846b28..a0e4c6e85 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -15,6 +15,7 @@ class ClanUpdatesHandler { // @TODO - BETA - Consider extend TeamUpdatesHandler. let clientsKeys = Object.keys(clan.clients); + // console.log({updateClanPlayersClients: clientsKeys}); if (0 === clientsKeys.length) { Logger.info('Clan update without clients.', clan); return false; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index b7e7b4464..7b62c9427 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -44,6 +44,7 @@ class CreatePlayerClanHandler if(!endEvent.continueProcess){ return false; } + // console.log('CreatePlayerClanHandler::enrichPlayerWithClan'); return ClanUpdatesHandler.updateClanPlayers(clan); } diff --git a/lib/teams/server/event-handlers/create-player-team-handler.js b/lib/teams/server/event-handlers/create-player-team-handler.js index c6ca81ccf..e3b446a52 100644 --- a/lib/teams/server/event-handlers/create-player-team-handler.js +++ b/lib/teams/server/event-handlers/create-player-team-handler.js @@ -12,7 +12,7 @@ class CreatePlayerTeamHandler static async joinExistentTeam(client, playerSchema, teamsPlugin) { - let teamId = teamsPlugin.changingRoomPlayers[playerSchema.player_id]?.teamId; + let teamId = teamsPlugin.teamChangingRoomPlayers[playerSchema.player_id]?.teamId; if(!teamId){ Logger.info('Player "'+playerSchema.playerName+'" (ID "'+playerSchema.player_id+'"), is not in a team.'); return false; diff --git a/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js index 6bacbb3e8..e9b696475 100644 --- a/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js +++ b/lib/teams/server/event-handlers/end-player-hit-change-point-team-handler.js @@ -16,7 +16,7 @@ class EndPlayerHitChangePointTeamHandler Logger.info('Player "'+playerSchema.playerName+'" (ID "'+playerSchema.player_id+'"), team not saved.'); return false; } - teamsPlugin.changingRoomPlayers[playerSchema.player_id] = { + teamsPlugin.teamChangingRoomPlayers[playerSchema.player_id] = { teamId, leavingPlayerSessionId: playerSchema.sessionId }; diff --git a/lib/teams/server/event-handlers/stats-update-handler.js b/lib/teams/server/event-handlers/stats-update-handler.js index ff27a00b4..2578c1d42 100644 --- a/lib/teams/server/event-handlers/stats-update-handler.js +++ b/lib/teams/server/event-handlers/stats-update-handler.js @@ -14,32 +14,31 @@ class StatsUpdateHandler static async updateTeam(props) { - let {teamsPlugin, playerSchema} = props; - let currentTeamId = sc.get(playerSchema, 'currentTeam', ''); + let currentTeamId = sc.get(props.playerSchema, 'currentTeam', ''); if('' === currentTeamId){ - return; + return false; } - let currentTeam = sc.get(teamsPlugin.teams, currentTeamId, false); + let currentTeam = sc.get(props.teamsPlugin.teams, currentTeamId, false); if(!currentTeam){ Logger.error('Team not found: '+ currentTeamId); - return; + return false; } return TeamUpdatesHandler.updateTeamPlayers(currentTeam); } static async updateClan(props) { - let {teamsPlugin, playerSchema} = props; - let clanId = sc.get(playerSchema.privateData.clan, 'id', ''); - if('' === clanId){ - return; + let clanId = props.playerSchema?.privateData?.clan; + if(!clanId){ + return false; } - let currentClan = sc.get(teamsPlugin.clans, clanId, false); - if(!currentClan){ + let clan = sc.get(props.teamsPlugin.clans, clanId, false); + if(!clan){ Logger.error('Clan not found: '+ clanId); - return; + return false; } - return ClanUpdatesHandler.updateClanPlayers(currentClan); + // console.log('StatsUpdateHandler::updateClan'); + return ClanUpdatesHandler.updateClanPlayers(clan); } } diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 580d892b7..469f6e3d1 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -58,6 +58,10 @@ class ClanDisconnect Logger.info('Stopped event "clanDisconnectAfterSendUpdate".'); return false; } + if(playerSchema.physicalBody.isChangingScene){ + Logger.info('Player is changing scene, avoid disconnect update.'); + return false; + } return ClanUpdatesHandler.updateClanPlayers(clan); } diff --git a/lib/teams/server/plugin.js b/lib/teams/server/plugin.js index 06643c8f3..bfac477ba 100644 --- a/lib/teams/server/plugin.js +++ b/lib/teams/server/plugin.js @@ -30,7 +30,8 @@ class TeamsPlugin extends PluginInterface } this.teams = sc.get(props, 'teams', {}); this.clans = sc.get(props, 'clans', {}); - this.changingRoomPlayers = sc.get(props, 'changingRoomPlayers', {}); + this.teamChangingRoomPlayers = sc.get(props, 'teamChangingRoomPlayers', {}); + this.clanChangingRoomPlayers = sc.get(props, 'clanChangingRoomPlayers', {}); this.events.on('reldens.roomsMessageActionsGlobal', (roomMessageActions) => { roomMessageActions.teams = new TeamMessageActions({teamsPlugin: this}); roomMessageActions.clan = new ClanMessageActions({teamsPlugin: this}); diff --git a/lib/users/client/player-engine.js b/lib/users/client/player-engine.js index a8335970c..f976f6037 100644 --- a/lib/users/client/player-engine.js +++ b/lib/users/client/player-engine.js @@ -131,6 +131,7 @@ class PlayerEngine let playerSprite = this.players[playerId]; if(!playerSprite.anims){ Logger.error('PlayerSprite animation not defined.'); + return; } if(this.scene.clientInterpolation){ this.scene.interpolatePlayersPosition[playerId] = player.state; From 2507cbe9ac66a67629c40aabb470af6925669832 Mon Sep 17 00:00:00 2001 From: Lucio Vicentini Date: Tue, 18 Apr 2023 15:28:30 +0200 Subject: [PATCH 46/82] Make Played time shown or not configurable. --- lib/game/client/game-engine.js | 17 ++++++++++------- lib/game/constants.js | 5 +++++ lib/rooms/server/scene.js | 21 ++++----------------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/game/client/game-engine.js b/lib/game/client/game-engine.js index 6d48fd250..184ae6dca 100644 --- a/lib/game/client/game-engine.js +++ b/lib/game/client/game-engine.js @@ -93,24 +93,27 @@ class GameEngine extends Game { // @TODO - BETA - Refactor. let gameManager = this.uiScene.gameManager; + if(GameConst.TYPE_PLAYER !== target.type){ + return targetName; + } let showPlayedTime = gameManager.config.get('client/players/playedTime/show'); - if(0 === showPlayedTime || GameConst.TYPE_PLAYER !== target.type){ + if (GameConst.SHOW_PLAYER_TIME.NONE === showPlayedTime) { return targetName; } let currentPlayer = gameManager.getCurrentPlayer(); - let timeText = ''; let label = gameManager.config.get('client/players/playedTime/label'); - if(0 < showPlayedTime && currentPlayer.playerId === target.id){ + let targetDisplayContent = targetName; + if(GameConst.SHOW_PLAYER_TIME.ONLY_OWN_PLAYER === showPlayedTime && currentPlayer.playerId === target.id){ let element = gameManager.gameDom.createElement('p'); element.innerHTML = label+(currentPlayer.playedTime / 60 / 60).toFixed(1)+'hs'; - timeText = element.outerHTML; + targetDisplayContent += element.outerHTML; } - if(2 === showPlayedTime && sc.hasOwn(currentPlayer.players, target.id)){ + if(GameConst.SHOW_PLAYER_TIME.ALL_PLAYERS === showPlayedTime && sc.hasOwn(currentPlayer.players, target.id)){ let element = gameManager.gameDom.createElement('p'); element.innerHTML = label+(currentPlayer.players[target.id].playedTime / 60 / 60).toFixed(1)+'hs'; - timeText = element.outerHTML; + targetDisplayContent += element.outerHTML; } - return targetName+timeText; + return targetDisplayContent; } clearTarget() diff --git a/lib/game/constants.js b/lib/game/constants.js index 30ec64282..7c5da5080 100644 --- a/lib/game/constants.js +++ b/lib/game/constants.js @@ -60,5 +60,10 @@ module.exports.GameConst = { GRAPHICS: { FRAME_WIDTH: 32, FRAME_HEIGHT: 32 + }, + SHOW_PLAYER_TIME: { + NONE: -1, + ONLY_OWN_PLAYER: 0, + ALL_PLAYERS: 2, } }; diff --git a/lib/rooms/server/scene.js b/lib/rooms/server/scene.js index 0309a79b6..269761fcb 100644 --- a/lib/rooms/server/scene.js +++ b/lib/rooms/server/scene.js @@ -351,7 +351,7 @@ class RoomScene extends RoomLogin broadcastSceneChange(client, currentPlayer, data) { // @NOTE: we need to broadcast the current player scene change to be removed or added in other players. - this.broadcast('*', { + let messageData = { act: GameConst.CHANGED_SCENE, id: client.sessionId, scene: currentPlayer.state.scene, @@ -360,23 +360,10 @@ class RoomScene extends RoomLogin y: currentPlayer.state.y, dir: currentPlayer.state.dir, playerName: currentPlayer.playerName, - playedTime: currentPlayer.playedTime, + playedTime: GameConst.SHOW_PLAYER_TIME.ALL_PLAYERS === this.config.getWithoutLogs('client/players/playedTime/show', GameConst.SHOW_PLAYER_TIME.NONE) ? currentPlayer.playedTime : -1, avatarKey: currentPlayer.avatarKey - }); - /* @TODO - BETA - Make played time broadcast optional. - this.send('*', { - act: GameConst.CHANGED_SCENE, - id: client.sessionId, - scene: currentPlayer.state.scene, - prev: data.prev, - x: currentPlayer.state.x, - y: currentPlayer.state.y, - dir: currentPlayer.state.dir, - playerName: currentPlayer.playerName, - playedTime: currentPlayer.playedTime, - avatarKey: currentPlayer.avatarKey - }); - */ + }; + this.broadcast('*', messageData); // remove body from server world: let bodyToRemove = currentPlayer.physicalBody; this.roomWorld.removeBody(bodyToRemove); From 117d31cdd088cb872ac11ade3e59113f0855dba6 Mon Sep 17 00:00:00 2001 From: "Damian A. Pastorini" Date: Wed, 19 Apr 2023 20:27:06 +0200 Subject: [PATCH 47/82] - Reldens - v4.0.0 - Clan updates fixed. - Audio error message fix. - Player sprite error message fix. --- lib/audio/client/manager.js | 11 ++++++++--- lib/objects/client/plugin.js | 6 +++++- lib/teams/client/clan-message-handler.js | 3 ++- lib/teams/client/clan-message-listener.js | 1 - lib/teams/client/team-message-handler.js | 3 ++- lib/teams/server/clan-updates-handler.js | 1 - .../event-handlers/create-player-clan-handler.js | 1 - .../server/event-handlers/stats-update-handler.js | 1 - lib/teams/server/message-actions/clan-disconnect.js | 10 +++++----- lib/teams/server/message-actions/team-leave.js | 2 +- lib/users/client/player-engine.js | 4 ++-- 11 files changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/audio/client/manager.js b/lib/audio/client/manager.js index ca2cee598..2641057b6 100644 --- a/lib/audio/client/manager.js +++ b/lib/audio/client/manager.js @@ -72,8 +72,10 @@ class AudioManager { let soundConfig = Object.assign({}, this.defaultAudioConfig, (audio.config || {})); if(!sc.hasOwn(onScene.cache.audio.entries.entries, audio.audio_key)){ - // @TODO - BETA - Check this error. - Logger.error('Audio file does not exists. Audio key:', audio.audio_key); + Logger.error( + 'Audio file does not exists. Audio key: ' + audio.audio_key, + onScene.cache.audio.entries.entries + ); return false; } let audioInstance = onScene.sound.add(audio.audio_key, soundConfig); @@ -171,7 +173,10 @@ class AudioManager continue; } let audioLoader = currentScene.load.audio(audio.audio_key, filesArr); - audioLoader.on('complete', async () => { + audioLoader.on('filecomplete', async (completedFileKey) => { + if(completedFileKey !== audio.audio_key){ + return false; + } let generateAudio = this.generateAudio(currentScene, audio); if(false === generateAudio){ Logger.error('AudioLoader can not generate the audio.', { diff --git a/lib/objects/client/plugin.js b/lib/objects/client/plugin.js index 1619886ed..a86d789ec 100644 --- a/lib/objects/client/plugin.js +++ b/lib/objects/client/plugin.js @@ -79,7 +79,11 @@ class ObjectsPlugin extends PluginInterface if(sc.hasOwn(currentScene.anims.anims.entries, skillBullet)){ animKey = skillBullet; } - let bulletSprite = currentScene.physics.add.sprite(body.x, body.y, animKey); + let bulletSprite = currentScene?.physics?.add?.sprite(body.x, body.y, animKey); + if(!bulletSprite){ + Logger.warning('Could not create bullet sprite.', currentScene); + return false; + } bulletSprite.setDepth(300000); this.bullets[key] = bulletSprite; } diff --git a/lib/teams/client/clan-message-handler.js b/lib/teams/client/clan-message-handler.js index 12b5654ab..74ebc1f9e 100644 --- a/lib/teams/client/clan-message-handler.js +++ b/lib/teams/client/clan-message-handler.js @@ -35,7 +35,8 @@ class ClanMessageHandler return false; } if(!this.uiScene){ - Logger.info('Missing UI Scene on ClanMessageHandler.'); + // @NOTE: the message could arrive before the uiScene gets ready. + // Logger.info('Missing UI Scene on ClanMessageHandler.'); return false; } return this.gameManager.getCurrentPlayer().playerId; diff --git a/lib/teams/client/clan-message-listener.js b/lib/teams/client/clan-message-listener.js index e9c16b344..7b62f2716 100644 --- a/lib/teams/client/clan-message-listener.js +++ b/lib/teams/client/clan-message-listener.js @@ -41,7 +41,6 @@ class ClanMessageListener handleClanMessage(message, clanMessageHandler) { - // console.log({message}); if(TeamsConst.ACTIONS.CLAN_INITIALIZE === message.act){ return clanMessageHandler.initializeClanUi(); } diff --git a/lib/teams/client/team-message-handler.js b/lib/teams/client/team-message-handler.js index 4fa521b3f..41dc773a0 100644 --- a/lib/teams/client/team-message-handler.js +++ b/lib/teams/client/team-message-handler.js @@ -35,7 +35,8 @@ class TeamMessageHandler return false; } if(!this.uiScene){ - Logger.info('Missing UI Scene on TeamMessageHandler.'); + // @NOTE: the message could arrive before the uiScene gets ready. + // Logger.info('Missing UI Scene on TeamMessageHandler.'); return false; } return true; diff --git a/lib/teams/server/clan-updates-handler.js b/lib/teams/server/clan-updates-handler.js index a0e4c6e85..025846b28 100644 --- a/lib/teams/server/clan-updates-handler.js +++ b/lib/teams/server/clan-updates-handler.js @@ -15,7 +15,6 @@ class ClanUpdatesHandler { // @TODO - BETA - Consider extend TeamUpdatesHandler. let clientsKeys = Object.keys(clan.clients); - // console.log({updateClanPlayersClients: clientsKeys}); if (0 === clientsKeys.length) { Logger.info('Clan update without clients.', clan); return false; diff --git a/lib/teams/server/event-handlers/create-player-clan-handler.js b/lib/teams/server/event-handlers/create-player-clan-handler.js index 7b62c9427..b7e7b4464 100644 --- a/lib/teams/server/event-handlers/create-player-clan-handler.js +++ b/lib/teams/server/event-handlers/create-player-clan-handler.js @@ -44,7 +44,6 @@ class CreatePlayerClanHandler if(!endEvent.continueProcess){ return false; } - // console.log('CreatePlayerClanHandler::enrichPlayerWithClan'); return ClanUpdatesHandler.updateClanPlayers(clan); } diff --git a/lib/teams/server/event-handlers/stats-update-handler.js b/lib/teams/server/event-handlers/stats-update-handler.js index 2578c1d42..60c87efc1 100644 --- a/lib/teams/server/event-handlers/stats-update-handler.js +++ b/lib/teams/server/event-handlers/stats-update-handler.js @@ -37,7 +37,6 @@ class StatsUpdateHandler Logger.error('Clan not found: '+ clanId); return false; } - // console.log('StatsUpdateHandler::updateClan'); return ClanUpdatesHandler.updateClanPlayers(clan); } diff --git a/lib/teams/server/message-actions/clan-disconnect.js b/lib/teams/server/message-actions/clan-disconnect.js index 469f6e3d1..f4742c861 100644 --- a/lib/teams/server/message-actions/clan-disconnect.js +++ b/lib/teams/server/message-actions/clan-disconnect.js @@ -18,6 +18,10 @@ class ClanDisconnect Logger.info('Player already left, wont disconnect from clan.'); return false; } + if(playerSchema?.physicalBody?.isChangingScene){ + Logger.info('Player is changing scene, avoid disconnect update.'); + return false; + } let clanId = playerSchema?.privateData?.clan; if(!clanId){ Logger.warning('Clan ID not found in current player for disconnection.', playerSchema); @@ -31,7 +35,7 @@ class ClanDisconnect // @NOTE: the way this works is by making the clients leave the clan and then updating the remaining players. let playerId = playerSchema.player_id; let client = clan.clients[playerId]; - if(1 === client.ref.readyState){ + if(1 === client?.ref?.readyState){ let sendUpdate = { act: TeamsConst.ACTIONS.CLAN_LEFT, id: clan.ownerClient.id, @@ -58,10 +62,6 @@ class ClanDisconnect Logger.info('Stopped event "clanDisconnectAfterSendUpdate".'); return false; } - if(playerSchema.physicalBody.isChangingScene){ - Logger.info('Player is changing scene, avoid disconnect update.'); - return false; - } return ClanUpdatesHandler.updateClanPlayers(clan); } diff --git a/lib/teams/server/message-actions/team-leave.js b/lib/teams/server/message-actions/team-leave.js index 6c2d0eb38..886ac3e7c 100644 --- a/lib/teams/server/message-actions/team-leave.js +++ b/lib/teams/server/message-actions/team-leave.js @@ -37,7 +37,7 @@ class TeamLeave let playerIds = Object.keys(currentTeam.players); let removeByKeys = playerSchema.id === teamId || 2 >= playerIds.length ? playerIds : [singleRemoveId]; for(let playerId of removeByKeys){ - if(1 === currentTeam.clients[playerId].ref.readyState){ + if(1 === currentTeam.clients[playerId]?.ref?.readyState){ let sendUpdate = { act: TeamsConst.ACTIONS.TEAM_LEFT, id: currentTeam.ownerClient.id, diff --git a/lib/users/client/player-engine.js b/lib/users/client/player-engine.js index f976f6037..416aadb0d 100644 --- a/lib/users/client/player-engine.js +++ b/lib/users/client/player-engine.js @@ -129,8 +129,8 @@ class PlayerEngine updatePlayer(playerId, player) { let playerSprite = this.players[playerId]; - if(!playerSprite.anims){ - Logger.error('PlayerSprite animation not defined.'); + if(!playerSprite){ + Logger.error('PlayerSprite not defined.', this.players, playerId); return; } if(this.scene.clientInterpolation){ From 8c48bb8dbaadbaf30557b6a8d5a48432836cb2a2 Mon Sep 17 00:00:00 2001 From: Lucio Vicentini Date: Thu, 20 Apr 2023 16:13:02 +0200 Subject: [PATCH 48/82] Show terms and conditions in register form. --- lib/game/server/login-manager.js | 18 ++++++++ migrations/production/beta.26-sql-update.sql | 22 ++++++++++ theme/default/css/styles.scss | 1 + theme/default/css/terms-conditions.scss | 44 ++++++++++++++++++++ theme/default/index.html | 22 +++++++++- theme/default/index.js | 32 +++++++++++++- 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 theme/default/css/terms-conditions.scss diff --git a/lib/game/server/login-manager.js b/lib/game/server/login-manager.js index 7a1fc9a4e..5894627e3 100644 --- a/lib/game/server/login-manager.js +++ b/lib/game/server/login-manager.js @@ -33,6 +33,15 @@ class LoginManager props.serverManager.app.get('/reldens-mailer-enabled', (req, res) => { res.json({enabled: this.mailer.isEnabled()}); }); + + this.declareLoginTermsAndConditionsRequestHandler(props.serverManager.app); + }); + } + + declareLoginTermsAndConditionsRequestHandler(app) + { + app.get('/terms-and-conditions', (req, res) => { + res.json(this.fetchTermsAndConditions()); }); } @@ -288,6 +297,15 @@ class LoginManager }); } + fetchTermsAndConditions() + { + let heading = this.config.get('client/login/terms_and_conditions/heading', false); + let body = this.config.get('client/login/terms_and_conditions/body', false); + return { + heading: heading, + body: body + } + } } module.exports.LoginManager = LoginManager; diff --git a/migrations/production/beta.26-sql-update.sql b/migrations/production/beta.26-sql-update.sql index 9837daa5c..eb31b3835 100644 --- a/migrations/production/beta.26-sql-update.sql +++ b/migrations/production/beta.26-sql-update.sql @@ -203,6 +203,28 @@ INSERT INTO `objects_items_rewards_animations` VALUES (1, 2, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'), (2, 1, 'spritesheet', 'branch-sprite', 'branch-sprite', '{"start":0,"end":2,"repeat":-1,"frameWidth":32, "frameHeight":32,"depthByPlayer":"above"}'); + +INSERT INTO `config` VALUES (NULL, 'client', 'login/terms_and_conditions/body', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vitae laoreet est, eget mattis justo. Donec urna sapien, tincidunt venenatis accumsan eu, molestie at ante. Suspendisse sed semper lacus. Mauris porttitor urna id odio bibendum, non ornare justo finibus. Donec vestibulum condimentum ante, non malesuada nulla porta eu. Proin scelerisque nibh at dolor mattis, sed laoreet nibh gravida. Interdum et malesuada fames ac ante ipsum primis in faucibus. In hac habitasse platea dictumst. Nullam euismod lorem vel imperdiet porttitor. Sed sapien massa, dapibus vitae ante ut, elementum scelerisque diam. Quisque a tempor dui. Suspendisse semper, nibh in faucibus tincidunt, orci est sollicitudin magna, eu ullamcorper dui lacus non ipsum. Praesent vitae orci a eros maximus condimentum ac a quam. In sodales velit lorem, id finibus lacus tempor quis. + + Duis euismod libero dui, sed tristique augue mattis eget. Etiam eu tristique velit. Cras id urna condimentum lorem luctus suscipit. Aliquam id facilisis justo. Praesent id iaculis sem, sed venenatis dui. Vestibulum sagittis ex in tellus accumsan varius. Morbi dapibus ante sit amet neque sollicitudin aliquam. Mauris dignissim quam et arcu tincidunt, sed dignissim dui viverra. Sed libero urna, ornare nec blandit accumsan, dignissim at diam. Fusce eget purus vel sapien dignissim viverra ut in est. Duis ut porttitor sem, vel dictum est. Praesent consectetur dictum nibh, et elementum neque imperdiet eget. Donec non convallis nulla, quis pulvinar diam. Mauris eu eros eu orci cursus ultrices. + + Vestibulum maximus felis vestibulum scelerisque varius. Nunc consectetur orci quis nisi imperdiet, et bibendum metus blandit. Donec sagittis enim purus, vulputate aliquam diam consectetur vel. Aliquam bibendum lorem diam, vitae feugiat erat pharetra a. Pellentesque mattis placerat sem sit amet tempor. Suspendisse feugiat arcu at sem posuere mollis. In hendrerit ligula at mauris ultricies dictum. Nunc quam diam, condimentum at nisl ultrices, consectetur lobortis tortor. + + Quisque erat quam, sodales quis metus ultricies, laoreet dictum leo. Integer rhoncus, nisi at porta varius, ipsum nibh consequat tortor, quis viverra sapien dolor a nulla. Sed vel volutpat purus. Nam eu placerat nulla. Fusce fringilla hendrerit commodo. Quisque maximus in turpis id eleifend. Maecenas a mollis sem. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec quis nisl ligula. + + Vivamus pellentesque, felis quis malesuada condimentum, metus lectus vehicula mi, accumsan laoreet nunc felis vel turpis. Suspendisse vel sagittis nisi. Nam vel est a neque egestas malesuada luctus eget est. Proin pretium, turpis a venenatis pulvinar, lacus tellus porta turpis, eu gravida nisi orci a ipsum. Aenean ac pharetra massa. Vivamus nec libero vehicula, egestas ligula eget, bibendum neque. Maecenas porttitor turpis id ante mattis vulputate. Curabitur accumsan, mauris vitae accumsan pellentesque, sem arcu tincidunt eros, vitae porttitor dui est et sem. Etiam semper vitae tortor nec vehicula. Suspendisse lobortis tristique fermentum. Praesent sed arcu hendrerit, accumsan lectus commodo, lobortis orci. Sed rutrum elit eu leo tincidunt, non scelerisque risus aliquet. Pellentesque pulvinar sagittis tempus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer fringilla purus eget accumsan malesuada. + + Nulla faucibus nunc et tellus commodo auctor. Donec semper ligula nec turpis congue finibus. Suspendisse feugiat cursus tellus sed tempus. Proin malesuada a purus at maximus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi at dolor nec lectus cursus eleifend eu eu magna. Quisque a condimentum erat, tincidunt interdum metus. Maecenas egestas tempus velit. Nam suscipit elit eu orci dictum, in luctus purus porttitor. Ut ullamcorper dui nisl, vel auctor mauris gravida non. + + Nulla sit amet vestibulum lacus, ac efficitur odio. Donec a nibh fermentum, imperdiet leo pulvinar, hendrerit odio. Quisque euismod, augue id laoreet molestie, quam mauris lobortis elit, sit amet sodales lectus massa in ipsum. Mauris viverra eu tellus id feugiat. Phasellus feugiat, magna in malesuada malesuada, tellus diam eleifend massa, et porttitor velit ante ac diam. Vestibulum consectetur vitae lectus sed tincidunt. Cras finibus faucibus enim, vel rutrum purus convallis non. In euismod lectus in augue finibus imperdiet vel id mi. In leo mauris, semper vel consequat ut, vulputate a libero. Curabitur ornare eros vel mollis molestie. Vivamus vel sapien eu odio laoreet posuere ac quis mi. Morbi euismod tortor vel purus consectetur egestas. Vivamus quam lectus, fringilla et rutrum id, interdum in risus. Sed pharetra lacinia nunc, id accumsan tellus tristique a. + + Aliquam euismod sodales sapien vitae finibus. Nulla facilisi. Vivamus ut odio pulvinar, vehicula lectus sed, faucibus metus. In lacinia est ante, in laoreet magna efficitur eu. Phasellus sollicitudin faucibus magna at suscipit. Pellentesque mattis orci at nisl pulvinar commodo. Nam magna tellus, dictum nec leo at, cursus imperdiet ligula. Aenean efficitur porta commodo. Nulla accumsan felis sed lacus gravida cursus. + + Praesent at tortor vitae ex placerat suscipit. Donec viverra tortor vel suscipit tristique. In elit libero, placerat vitae eros non, lobortis feugiat justo. Donec nec tellus et odio posuere hendrerit. Nullam vulputate, velit eget tincidunt sodales, est mi lobortis lacus, nec blandit eros orci sit amet lacus. Nam in luctus nunc. Proin a enim urna. Morbi vitae ipsum vel velit fermentum accumsan nec ut ante. Maecenas libero nulla, consequat eget diam nec, efficitur vulputate ante. Curabitur in efficitur nulla. + + Ut sit amet purus mi. Donec eget orci scelerisque, tempus odio sagittis, laoreet sapien. Aliquam venenatis et libero in facilisis. Phasellus fermentum diam ante, sit amet ultrices nibh hendrerit at. Pellentesque aliquet, ante et efficitur imperdiet, eros risus pharetra nisl, non congue sapien felis nec ligula. Vivamus vehicula libero et mi vulputate pretium. Nunc rhoncus aliquam tortor, sit amet dignissim diam maximus non. Donec imperdiet augue sem, eu ultricies augue lacinia consectetur. Donec leo enim, tempor vitae odio nec, vestibulum gravida est. Proin id dui dictum, faucibus sapien sed, malesuada ligula. Curabitur fermentum ullamcorper arcu nec sollicitudin. Sed enim nisl, fringilla quis sapien vel, imperdiet tempus justo. Donec id porttitor nibh, ut ultrices tortor.', @string_id); +INSERT INTO `config` VALUES (NULL, 'client', 'login/terms_and_conditions/heading', 'Terminos y Condiciones', @string_id); + ####################################################################################################################### SET FOREIGN_KEY_CHECKS = 1; diff --git a/theme/default/css/styles.scss b/theme/default/css/styles.scss index fa59db5e1..2163dc43a 100644 --- a/theme/default/css/styles.scss +++ b/theme/default/css/styles.scss @@ -23,3 +23,4 @@ $cBlack: #000; @import "chat"; @import "items-system"; @import "teams"; +@import "terms-conditions"; diff --git a/theme/default/css/terms-conditions.scss b/theme/default/css/terms-conditions.scss new file mode 100644 index 000000000..ecc86f018 --- /dev/null +++ b/theme/default/css/terms-conditions.scss @@ -0,0 +1,44 @@ +/** + * + * Reldens - Styles - Terms and Conditions + * + */ + +#terms-conditions { + z-index: 2000; + position: absolute; + top: 20%; + overflow: visible; + width: 46%; + margin: 0; + left: 25%; + right: auto; + background-color: rgba(0,0,0,0.7); + color: $cWhite; + cursor: default; + + .scrollable { + overflow-y: scroll; + max-height: 280px; + padding: 2% 4%; + } + + .terms-conditions-text { + display: block; + float: left; + width: 100%; + overflow-x: hidden; + overflow-y: auto; + margin-bottom: 15px; + + h3 { + text-align: center; + } + } +} + +.tacbox { + display: block; + padding: 1%; + margin: 2%; +} diff --git a/theme/default/index.html b/theme/default/index.html index a04558364..bb7db20aa 100644 --- a/theme/default/index.html +++ b/theme/default/index.html @@ -90,6 +90,9 @@

Create Account

+
@@ -97,7 +100,7 @@

Create Account

Loading...
@@ -122,6 +125,23 @@

Forgot password