diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 316eada..ddeda82 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,2 +1 @@ -- Исправлено название "Автоматического" оружия на "Механическое". -- Добавлена возможность экспортировать и импортировать данные персонажа. \ No newline at end of file +- Добавлен маленький и приятный функционал для нпс и гм. \ No newline at end of file diff --git a/interface/scripted/collections/collectionsgui.config b/interface/scripted/collections/collectionsgui.config index 1b1b8f1..dfafaf9 100644 --- a/interface/scripted/collections/collectionsgui.config +++ b/interface/scripted/collections/collectionsgui.config @@ -1220,6 +1220,22 @@ "defaultTooltip": "Скрывать бонусы" } }, + + "btnSpawnHealthBar" : { + "type": "button", + "checkable": true, + "checked": false, + "position": [135, 18], + "base": "/interface/optionsmenu/checkboxnocheck.png", + "hover": "/interface/optionsmenu/checkboxnocheckhover.png", + "baseImageChecked": "/interface/optionsmenu/checkboxcheck.png", + "hoverImageChecked": "/interface/optionsmenu/checkboxcheckhover.png", + "callback": "spawnHealthBar", + "data": { + "defaultTooltip": "Показать полосу здоровья" + } + }, + "tbxStatStrength": { "type": "textbox", "position": [ @@ -5625,91 +5641,29 @@ 2 ] } - }, - "scriptWidgetCallbacks": [ - "statChange", - "changeMeleeWeapon", - "changeTab", - "changeAttackType", - "changeBonusType", - "gearChange", - "lineSelected", - "addBonus", - "changeHp", - "rollStat", - "changeBonusLayout", - "attack", - "defense", - "resetBaseBonuses", - "roll20", - "playerSelected", - "addBonusGroup", - "enterFight", - "leaveFight", - "nextTurn", - "clearFight", - "changeFightName", - "changeBonus.up", - "changeBonus.down", - "resources", - "showCurrentPlayer", - "hideBonusesInGroup", - "addAttack", - "showAddAttackLayout", - "addEffectsToAttack", - "showMovement", - "deleteAttack", - "changeNewBonus", - "changeRollMode", - "setRollMode", - "restoreHp", - "attackFixedDamage", - "actionSuccess", - "actionFail", - "setAttackType", - "hideDamage", - "selfBonus", - "editHP", - "savePreset", - "changePreset", - "registerItem", - "editMode", - "editSave", - "editClose", - "restoreStamina", - "changeStamina", - "accept_damage", - "decline_damage", - "closeEffectsToAttack", - "searchBonus", - "createInventoryItem", - "removeInventoryItem", - "addInventoryItemAmount", - "subtractInventoryItemAmount", - "filterInventoryItems", - "printMoney", - "sendMoney", - "sendAttacks", - "cancelAttack", - "resetResourceAttempts", - "updateResourceSlider", - "findFightToManage", - "forceNextTurn", - "kickFromFight", - "finishFight", - "changeInitiative", - "prepareChangeInitiative", - "exportData", - "importData" - ], - "characterStatsTextboxes": { - "tbxStatStrength": "strength", - "tbxStatEndurance": "endurance", - "tbxStatPerception": "perception", - "tbxStatReflexes": "reflexes", - "tbxStatMagic": "magic", - "tbxStatWillpower": "willpower", - "tbxStatIntellect": "intellect", + }, + "scriptWidgetCallbacks" : [ + "statChange", "changeMeleeWeapon", "changeTab", "changeAttackType", "changeBonusType", "gearChange", + "lineSelected", "addBonus", "changeHp", "rollStat", "changeBonusLayout", "attack", "defense", "resetBaseBonuses", + "roll20", "playerSelected", "addBonusGroup", "enterFight", "leaveFight", "nextTurn", "clearFight", + "changeFightName", "changeBonus.up", "changeBonus.down", "resources", "showCurrentPlayer", "hideBonusesInGroup", + "addAttack", "showAddAttackLayout", "addEffectsToAttack", "showMovement", "deleteAttack", "changeNewBonus", + "changeRollMode", "setRollMode", "restoreHp", "attackFixedDamage", "actionSuccess", "actionFail", "setAttackType", "hideDamage", + "selfBonus", "editHP", "savePreset", "changePreset", "registerItem", "editMode", "editSave", "editClose", + "restoreStamina", "changeStamina", "accept_damage", "decline_damage", "closeEffectsToAttack", "searchBonus", + "createInventoryItem", "removeInventoryItem", "addInventoryItemAmount", "subtractInventoryItemAmount", "filterInventoryItems", + "printMoney", "sendMoney", "sendAttacks", "cancelAttack", "resetResourceAttempts", "updateResourceSlider", + "findFightToManage", "forceNextTurn", "kickFromFight", "finishFight", "changeInitiative", "prepareChangeInitiative","spawnHealthBar" + ], + + "characterStatsTextboxes": { + "tbxStatStrength": "strength", + "tbxStatEndurance": "endurance", + "tbxStatPerception": "perception", + "tbxStatReflexes": "reflexes", + "tbxStatMagic": "magic", + "tbxStatWillpower": "willpower", + "tbxStatIntellect": "intellect", "tbxStatDetermination": "determination" }, "characterStatBonuses": [ @@ -5732,20 +5686,13 @@ "INT": "intellect", "DET": "determination", "NO": "" - }, - "tabs": [ - "lytCharacter", - "lytArmory", - "lytAttacks", - "lytMisc", - "lytWhoAttack", - "lytInventory", - "lytMoney", - "lytResources", - "lytFightManager" - ], + }, + + "tabs": [ + "lytCharacter", "lytArmory", "lytAttacks", "lytMisc", "lytWhoAttack", "lytInventory", "lytMoney", "lytResources", "lytFightManager" + ], + "version": "4.25.5", - "scripts": [ - "/interface/scripted/irdenstatmanager/irdenstatmanager.lua" - ] + + "scripts" : [ "/interface/scripted/irdenstatmanager/irdenstatmanager.lua" ] } \ No newline at end of file diff --git a/interface/scripted/irdenstatmanager/irdenstatmanager.lua b/interface/scripted/irdenstatmanager/irdenstatmanager.lua index eef451e..34c43f5 100644 --- a/interface/scripted/irdenstatmanager/irdenstatmanager.lua +++ b/interface/scripted/irdenstatmanager/irdenstatmanager.lua @@ -18,6 +18,7 @@ function init() widget.setButtonEnabled("lytCharacter.btnClearFight", player.isAdmin()) widget.setButtonEnabled("rgTabs.6", player.isAdmin()) widget.setVisible("lytCharacter.btnHideStats", player.isAdmin()) + widget.setVisible("lytCharacter.btnSpawnHealthBar", player.isAdmin()) widget.setVisible("lytCharacter.btnEnterFightAsEnemy", player.isAdmin()) widget.registerMemberCallback("lytMisc.lytBonuses.lytBaseBonuses.saBonuses.listBonuses", "setBonus", setBonus) @@ -722,6 +723,7 @@ function changeHp() self.irden.currentHp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 local newValue = self.irden.currentHp / maxHp + updateHealthBar(self.irden.currentHp, maxHp) if (newValue ~= oldHP or not self.firstDraw) then local aPrgCurrentHp = AnimatedWidget:bind("lytCharacter.prgCurrentHp") @@ -1215,6 +1217,91 @@ function resetBaseBonuses() loadAttacks() end + + +function ensureHealthbarMonster(callback) + if self.healthbarMonster and world.entityExists(self.healthbarMonster) then + if callback then callback(self.healthbarMonster, true) end + return + end + + local monsters = world.monsterQuery(world.entityPosition(player.id()), 5) + + if #monsters == 0 then + if callback then callback(nil, false) end + return + end + + local found = false + local responsesExpected = #monsters + local responsesReceived = 0 + + for _, monster in pairs(monsters) do + promises:add(world.sendEntityMessage(monster, "isHealthbarMonster", player.id()), + function(result) + responsesReceived = responsesReceived + 1 + if result == true and not found then + found = true + self.healthbarMonster = monster + if callback then callback(monster, true) end + elseif responsesReceived >= responsesExpected and not found then + if callback then callback(nil, false) end + end + end, + function(_) + responsesReceived = responsesReceived + 1 + if responsesReceived >= responsesExpected and not found then + if callback then callback(nil, false) end + end + end) + end +end + + +function spawnHealthBar() + local checked = widget.getChecked("lytCharacter.btnSpawnHealthBar") + + if checked then + ensureHealthbarMonster(function(_, found) + if found then + return + end + local monsterData = root.assetJson("/interface/scripted/irdenstatmanager/monster/monster_config.json") + monsterData.parentEntity = player.id() + self.healthbarMonster = world.spawnMonster("pteropod", world.entityPosition(player.id()), monsterData) + local currhp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 + updateHealthBar(currhp, getMaxHp()) + end) + else + destroyHealthbar() + end +end + + +function updateHealthBar(currentHp, maxHp) + ensureHealthbarMonster(function(monsterId) + if monsterId then + world.callScriptedEntity(monsterId, "updateHealth", currentHp, maxHp) + else + end + end) +end + + +function destroyHealthbar() + ensureHealthbarMonster(function(monsterId) + if monsterId then + world.callScriptedEntity(monsterId, "destroy") + end + self.healthbarMonster = nil + end) +end + +function getMaxHp() + local maxHp = 20 + irdenUtils.addBonusToStat(self.irden["stats"]["endurance"], "END") + irdenUtils.addBonusToStat(0, "MAX_HEALTH") + return maxHp +end + function subtractHP(value, kind) local physArmour = irdenUtils.addBonusToStat(irdenUtils.getBonusByTag(widget.getSelectedData("lytArmory.rgArmour").armourBonus).value, "ARM") local magArm = irdenUtils.addBonusToStat(irdenUtils.getBonusByTag(widget.getSelectedData("lytArmory.rgAmulets").blockBonus).value, "MAGARM") @@ -1224,11 +1311,13 @@ function subtractHP(value, kind) if subhp then local currhp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 + local maxHp = getMaxHp() if kind ~= "HEAL" then widget.setText("lytCharacter.tbxCurrentHp", math.max(0, currhp - subhp)) + updateHealthBar(math.max(0, currhp - subhp), maxHp) else - local maxHp = 20 + irdenUtils.addBonusToStat(self.irden["stats"]["endurance"], "END") + irdenUtils.addBonusToStat(0, "MAX_HEALTH") widget.setText("lytCharacter.tbxCurrentHp", math.min(maxHp, currhp + subhp)) + updateHealthBar(math.min(maxHp, currhp + subhp), maxHp) end end end diff --git a/interface/scripted/irdenstatmanager/monster/custom_monster.lua b/interface/scripted/irdenstatmanager/monster/custom_monster.lua new file mode 100644 index 0000000..5f89b4d --- /dev/null +++ b/interface/scripted/irdenstatmanager/monster/custom_monster.lua @@ -0,0 +1,67 @@ +require "/monsters/monster.lua" + +function init() + self.parentEntity = config.getParameter("parentEntity") + + monster.setName(world.entityName(self.parentEntity) or "Boss") + message.setHandler("dreadwingDeath", function() + end) + message.setHandler("despawn", function() + end) + message.setHandler("killIt", function() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end) + + message.setHandler("isHealthbarMonster", function(_, playerId) + if playerId == self.parentEntity then + return nil + end + return true + end) + + monster.setDamageBar("special") + status.setPersistentEffects("invincibilityTech", { + { stat = "breathProtection", amount = 1 }, + { stat = "biomeheatImmunity", amount = 1 }, + { stat = "biomecoldImmunity", amount = 1 }, + { stat = "biomeradiationImmunity", amount = 1 }, + { stat = "lavaImmunity", amount = 1 }, + { stat = "poisonImmunity", amount = 1 }, + { stat = "tarImmunity", amount = 1 }, + { stat = "invulnerable", amount = 1 }, + { stat = "healthMultiplier", amount = 1 } + }) + self.health = status.resource("health") +end + +function update(dt) + mcontroller.setPosition(world.entityPosition(self.parentEntity)) + if not world.entityExists(self.parentEntity) then + destroy() + end +end + +function updateHealth(currentHealth, maxHealth) + local healthPercentage = (maxHealth - currentHealth) / maxHealth + local currentHp = self.health - (self.health * healthPercentage) + status.setResource("health", currentHp) + return true +end + +function destroy() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") +end + +function uninit() + monster.setDamageBar("none") +end \ No newline at end of file diff --git a/interface/scripted/irdenstatmanager/monster/monster_config.json b/interface/scripted/irdenstatmanager/monster/monster_config.json new file mode 100644 index 0000000..c8f1309 --- /dev/null +++ b/interface/scripted/irdenstatmanager/monster/monster_config.json @@ -0,0 +1,127 @@ +{ + "animationCustom": { + "transformationGroups": { + "body": { + "interpolated": true + } + }, + "animatedParts": { + "stateTypes": { + "body": { + "states": { + "idle": { + "properties": { + "persistentSound": "" + }, + "mode": "loop", + "cycle": 1, + "frames": 1 + } + }, + "default": "idle", + "priority": 0 + }, + "damage": { + "states": { + "none": { + "frames": 1 + }, + "leave": { + "cycle": 0.25, + "frames": 4 + }, + "stunned": { + "properties": { + "persistentSound": "" + }, + "mode": "loop", + "cycle": 0.15, + "frames": 2 + } + }, + "default": "none", + "priority": 3 + } + }, + "parts": { + "body": { + "properties": { + "fullbright": false, + "transformationGroups": ["body"], + "offset": [0, 0] + }, + "partStates": { + "body": { + "idle": { + "properties": { + "image": "" + } + }, + "walk": { + "properties": { + "image": "" + } + }, + "fall": { + "properties": { + "image": "" + } + } + }, + "damage": { + "leave": { + "properties": { + "image": "" + } + }, + "stunned": { + "properties": { + "image": "" + } + } + } + } + } + } + } + }, + "statusSettings": { + "stats": { + "maxHealth": { + "baseValue": 6000.0 + }, + "healthRegen" : { + "baseValue" : 0.0 + } + } + }, + "scripts": [ + "/interface/scripted/irdenstatmanager/monster/custom_monster.lua" + ], + "damageTeamType" : "ghostly", + "appliesEnvironmentStatusEffects" : false, + "appliesWeatherStatusEffects" : false, + "clientEntityMode": "ClientMasterAllowed", + "capturable": false, + "aggressive": false, + "level" : 2.14, + "metaBoundBox" : null, + "touchDamage" : { + "poly" : null + }, + "scale": 0.1, + "movementSettings": { + "collisionPoly" : [[0, 0], [0, 0], [0, 0], [0, 0]], + "airFriction": 0, + "ignorePlatformCollision": true, + "maxMovementPerStep": 1, + "bounceFactor": 0, + "collisionEnabled": false, + "gravityMultiplier": 0, + "liquidFriction": 0 + }, + "knockoutAnimationStates": { + "damage": "leave" + }, + "relocatable": false +} diff --git a/scripts/custom_monster.lua b/scripts/custom_monster.lua new file mode 100644 index 0000000..ef5344c --- /dev/null +++ b/scripts/custom_monster.lua @@ -0,0 +1,46 @@ +require "/monsters/monster.lua" + +function init() + self.parentEntity = config.getParameter("parentEntity") + message.setHandler("dreadwingDeath", function() + end) + message.setHandler("despawn", function() + end) + message.setHandler("killIt", function() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end) + vehicle.setInteractive(false) + monster.setDamageBar("special") + status.setPersistentEffects("invincibilityTech", { + { stat = "breathProtection", amount = 1 }, + { stat = "biomeheatImmunity", amount = 1 }, + { stat = "biomecoldImmunity", amount = 1 }, + { stat = "biomeradiationImmunity", amount = 1 }, + { stat = "lavaImmunity", amount = 1 }, + { stat = "poisonImmunity", amount = 1 }, + { stat = "tarImmunity", amount = 1 }, + { stat = "invulnerable", amount = 1 } + }) + monster.setName(world.entityName(self.parentEntity) or "Boss") +end + +function update(dt) + mcontroller.setPosition(world.entityMouthPosition(self.parentEntity)) + if not world.entityExists(self.parentEntity) then + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end +end + +function uninit() + monster.setDamageBar("none") +end \ No newline at end of file